scenv 0.6.1 → 0.7.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 +68 -87
- package/dist/index.d.cts +38 -32
- package/dist/index.d.ts +38 -32
- package/dist/index.js +66 -88
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,12 +25,16 @@ __export(index_exports, {
|
|
|
25
25
|
discoverContextPaths: () => discoverContextPaths,
|
|
26
26
|
getCallbacks: () => getCallbacks,
|
|
27
27
|
getContext: () => getContext,
|
|
28
|
+
getContextWritePath: () => getContextWritePath,
|
|
29
|
+
getInMemoryContext: () => getInMemoryContext,
|
|
28
30
|
getMergedContextValues: () => getMergedContextValues,
|
|
29
31
|
loadConfig: () => loadConfig,
|
|
30
32
|
parseScenvArgs: () => parseScenvArgs,
|
|
31
33
|
resetConfig: () => resetConfig,
|
|
34
|
+
resetInMemoryContext: () => resetInMemoryContext,
|
|
32
35
|
resetLogState: () => resetLogState,
|
|
33
|
-
scenv: () => scenv
|
|
36
|
+
scenv: () => scenv,
|
|
37
|
+
setInMemoryContext: () => setInMemoryContext
|
|
34
38
|
});
|
|
35
39
|
module.exports = __toCommonJS(index_exports);
|
|
36
40
|
|
|
@@ -40,19 +44,6 @@ var import_node_path = require("path");
|
|
|
40
44
|
|
|
41
45
|
// src/prompt-default.ts
|
|
42
46
|
var import_node_readline = require("readline");
|
|
43
|
-
function ask(message) {
|
|
44
|
-
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
rl.question(message, (answer) => {
|
|
47
|
-
rl.close();
|
|
48
|
-
resolve(answer.trim());
|
|
49
|
-
});
|
|
50
|
-
rl.on("error", (err) => {
|
|
51
|
-
rl.close();
|
|
52
|
-
reject(err);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
47
|
function defaultPrompt(name, defaultValue) {
|
|
57
48
|
const defaultStr = defaultValue !== void 0 && defaultValue !== null ? String(defaultValue) : "";
|
|
58
49
|
const message = defaultStr ? `Enter ${name} [${defaultStr}]: ` : `Enter ${name}: `;
|
|
@@ -70,17 +61,6 @@ function defaultPrompt(name, defaultValue) {
|
|
|
70
61
|
});
|
|
71
62
|
});
|
|
72
63
|
}
|
|
73
|
-
async function defaultAskWhetherToSave(name, _value) {
|
|
74
|
-
const answer = await ask(`Save "${name}" for next time? (y/n): `);
|
|
75
|
-
const v = answer.toLowerCase();
|
|
76
|
-
return v === "y" || v === "yes" || v === "1" || v === "true";
|
|
77
|
-
}
|
|
78
|
-
async function defaultAskContext(name, contextNames) {
|
|
79
|
-
const hint = contextNames.length > 0 ? ` (${contextNames.join(", ")})` : "";
|
|
80
|
-
const answer = await ask(`Save "${name}" to which context?${hint}: `);
|
|
81
|
-
if (answer) return answer;
|
|
82
|
-
return contextNames[0] ?? "default";
|
|
83
|
-
}
|
|
84
64
|
|
|
85
65
|
// src/config.ts
|
|
86
66
|
var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
|
|
@@ -91,7 +71,6 @@ var envKeyMap = {
|
|
|
91
71
|
SCENV_PROMPT: "prompt",
|
|
92
72
|
SCENV_IGNORE_ENV: "ignoreEnv",
|
|
93
73
|
SCENV_IGNORE_CONTEXT: "ignoreContext",
|
|
94
|
-
SCENV_SAVE_PROMPT: "shouldSavePrompt",
|
|
95
74
|
SCENV_SAVE_CONTEXT_TO: "saveContextTo",
|
|
96
75
|
SCENV_CONTEXT_DIR: "contextDir",
|
|
97
76
|
SCENV_LOG_LEVEL: "logLevel"
|
|
@@ -100,9 +79,7 @@ var programmaticConfig = {};
|
|
|
100
79
|
var programmaticCallbacks = {};
|
|
101
80
|
function getCallbacks() {
|
|
102
81
|
return {
|
|
103
|
-
defaultPrompt: programmaticCallbacks.defaultPrompt ?? defaultPrompt
|
|
104
|
-
onAskWhetherToSave: programmaticCallbacks.onAskWhetherToSave ?? defaultAskWhetherToSave,
|
|
105
|
-
onAskContext: programmaticCallbacks.onAskContext ?? defaultAskContext
|
|
82
|
+
defaultPrompt: programmaticCallbacks.defaultPrompt ?? defaultPrompt
|
|
106
83
|
};
|
|
107
84
|
}
|
|
108
85
|
function findConfigDir(startDir) {
|
|
@@ -125,11 +102,9 @@ function configFromEnv() {
|
|
|
125
102
|
out[configKey] = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
126
103
|
} else if (configKey === "ignoreEnv" || configKey === "ignoreContext") {
|
|
127
104
|
out[configKey] = val === "1" || val === "true" || val.toLowerCase() === "yes";
|
|
128
|
-
} else if (configKey === "prompt"
|
|
105
|
+
} else if (configKey === "prompt") {
|
|
129
106
|
const v = val.toLowerCase();
|
|
130
|
-
if (
|
|
131
|
-
out[configKey] = v;
|
|
132
|
-
if (configKey === "shouldSavePrompt" && (v === "always" || v === "never" || v === "ask"))
|
|
107
|
+
if (v === "always" || v === "never" || v === "fallback" || v === "no-env")
|
|
133
108
|
out[configKey] = v;
|
|
134
109
|
} else if (configKey === "saveContextTo") {
|
|
135
110
|
out.saveContextTo = val;
|
|
@@ -172,8 +147,6 @@ function loadConfigFile(configDir) {
|
|
|
172
147
|
out.ignoreContext = parsed.ignoreContext;
|
|
173
148
|
if (parsed.set && typeof parsed.set === "object" && !Array.isArray(parsed.set))
|
|
174
149
|
out.set = parsed.set;
|
|
175
|
-
if (typeof (parsed.shouldSavePrompt ?? parsed.savePrompt) === "string" && ["always", "never", "ask"].includes(parsed.shouldSavePrompt ?? parsed.savePrompt))
|
|
176
|
-
out.shouldSavePrompt = parsed.shouldSavePrompt ?? parsed.savePrompt;
|
|
177
150
|
if (typeof parsed.saveContextTo === "string")
|
|
178
151
|
out.saveContextTo = parsed.saveContextTo;
|
|
179
152
|
if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
|
|
@@ -264,7 +237,6 @@ var CONFIG_LOG_KEYS = [
|
|
|
264
237
|
"ignoreEnv",
|
|
265
238
|
"ignoreContext",
|
|
266
239
|
"set",
|
|
267
|
-
"shouldSavePrompt",
|
|
268
240
|
"saveContextTo",
|
|
269
241
|
"contextDir",
|
|
270
242
|
"logLevel"
|
|
@@ -288,7 +260,6 @@ function logConfigLoaded(config) {
|
|
|
288
260
|
if (config.ignoreContext === true) parts.push("ignoreContext=true");
|
|
289
261
|
if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
|
|
290
262
|
if (config.contextDir !== void 0) parts.push("contextDir=" + config.contextDir);
|
|
291
|
-
if (config.shouldSavePrompt !== void 0) parts.push("shouldSavePrompt=" + config.shouldSavePrompt);
|
|
292
263
|
log("info", "config loaded", parts.join(" "));
|
|
293
264
|
}
|
|
294
265
|
if (levelNum >= LEVEL_NUM.debug) {
|
|
@@ -311,6 +282,16 @@ function log(level, msg, ...args) {
|
|
|
311
282
|
var import_node_fs2 = require("fs");
|
|
312
283
|
var import_node_path2 = require("path");
|
|
313
284
|
var CONTEXT_SUFFIX = ".context.json";
|
|
285
|
+
var inMemoryContext = {};
|
|
286
|
+
function getInMemoryContext() {
|
|
287
|
+
return inMemoryContext;
|
|
288
|
+
}
|
|
289
|
+
function setInMemoryContext(key, value) {
|
|
290
|
+
inMemoryContext[key] = value;
|
|
291
|
+
}
|
|
292
|
+
function resetInMemoryContext() {
|
|
293
|
+
inMemoryContext = {};
|
|
294
|
+
}
|
|
314
295
|
function discoverContextPathsInternal(dir, found) {
|
|
315
296
|
let entries;
|
|
316
297
|
try {
|
|
@@ -378,6 +359,14 @@ function getMergedContextValues() {
|
|
|
378
359
|
const root = config.root ?? process.cwd();
|
|
379
360
|
const paths = discoverContextPaths(root);
|
|
380
361
|
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
|
+
}
|
|
381
370
|
for (const contextName of config.context ?? []) {
|
|
382
371
|
const filePath = paths.get(contextName);
|
|
383
372
|
if (!filePath) {
|
|
@@ -405,6 +394,9 @@ function getMergedContextValues() {
|
|
|
405
394
|
return out;
|
|
406
395
|
}
|
|
407
396
|
function getContextWritePath(contextName) {
|
|
397
|
+
if ((0, import_node_path2.isAbsolute)(contextName) || contextName.includes(import_node_path2.sep)) {
|
|
398
|
+
return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
|
|
399
|
+
}
|
|
408
400
|
const config = loadConfig();
|
|
409
401
|
const root = config.root ?? process.cwd();
|
|
410
402
|
const paths = discoverContextPaths(root);
|
|
@@ -413,6 +405,26 @@ function getContextWritePath(contextName) {
|
|
|
413
405
|
const saveDir = config.contextDir ? (0, import_node_path2.isAbsolute)(config.contextDir) ? config.contextDir : (0, import_node_path2.join)(root, config.contextDir) : root;
|
|
414
406
|
return (0, import_node_path2.join)(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
|
|
415
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
|
+
}
|
|
427
|
+
}
|
|
416
428
|
function writeToContext(contextName, key, value) {
|
|
417
429
|
const path = getContextWritePath(contextName);
|
|
418
430
|
let data = {};
|
|
@@ -509,6 +521,12 @@ function scenv(name, options = {}) {
|
|
|
509
521
|
return { raw, source: "env" };
|
|
510
522
|
}
|
|
511
523
|
}
|
|
524
|
+
const mem = getInMemoryContext();
|
|
525
|
+
if (mem[key] !== void 0) {
|
|
526
|
+
log("trace", `resolveRaw: in-memory hit key=${key}`);
|
|
527
|
+
const raw = resolveContextReference(mem[key], key);
|
|
528
|
+
return { raw, source: "context" };
|
|
529
|
+
}
|
|
512
530
|
if (!config.ignoreContext) {
|
|
513
531
|
log("trace", "resolveRaw: checking context");
|
|
514
532
|
const ctx = getMergedContextValues();
|
|
@@ -594,36 +612,11 @@ function scenv(name, options = {}) {
|
|
|
594
612
|
const final = validated.data;
|
|
595
613
|
if (wasPrompted) {
|
|
596
614
|
const config = loadConfig();
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const callbacks2 = getCallbacks();
|
|
602
|
-
if (typeof callbacks2.onAskWhetherToSave !== "function") {
|
|
603
|
-
throw new Error(
|
|
604
|
-
`shouldSavePrompt is "ask" but onAskWhetherToSave callback is not set. Configure callbacks via configure({ callbacks: { onAskWhetherToSave: ... } }).`
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
doSave = await callbacks2.onAskWhetherToSave(name, final);
|
|
608
|
-
} else {
|
|
609
|
-
doSave = true;
|
|
610
|
-
}
|
|
611
|
-
if (!doSave) return final;
|
|
612
|
-
const callbacks = getCallbacks();
|
|
613
|
-
const contextNames = config.context ?? [];
|
|
614
|
-
let ctxToSave;
|
|
615
|
-
if (config.saveContextTo === "ask") {
|
|
616
|
-
if (typeof callbacks.onAskContext !== "function") {
|
|
617
|
-
throw new Error(
|
|
618
|
-
`saveContextTo is "ask" but onAskContext callback is not set. Configure callbacks via configure({ callbacks: { onAskContext: ... } }).`
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
ctxToSave = await callbacks.onAskContext(name, contextNames);
|
|
622
|
-
} else {
|
|
623
|
-
ctxToSave = config.saveContextTo ?? contextNames[0] ?? "default";
|
|
615
|
+
setInMemoryContext(key, String(final));
|
|
616
|
+
if (config.saveContextTo) {
|
|
617
|
+
writeToContext(config.saveContextTo, key, String(final));
|
|
618
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
624
619
|
}
|
|
625
|
-
writeToContext(ctxToSave, key, String(final));
|
|
626
|
-
log("info", `Saved key=${key} to context ${ctxToSave}`);
|
|
627
620
|
}
|
|
628
621
|
return final;
|
|
629
622
|
}
|
|
@@ -644,22 +637,11 @@ function scenv(name, options = {}) {
|
|
|
644
637
|
throw new Error(errMsg);
|
|
645
638
|
}
|
|
646
639
|
const config = loadConfig();
|
|
647
|
-
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
throw new Error(
|
|
652
|
-
`saveContextTo is "ask" but onAskContext callback is not set. Configure callbacks via configure({ callbacks: { onAskContext: ... } }).`
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
contextName = await callbacks.onAskContext(
|
|
656
|
-
name,
|
|
657
|
-
config.context ?? []
|
|
658
|
-
);
|
|
640
|
+
setInMemoryContext(key, String(validated.data));
|
|
641
|
+
if (config.saveContextTo) {
|
|
642
|
+
writeToContext(config.saveContextTo, key, String(validated.data));
|
|
643
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
659
644
|
}
|
|
660
|
-
if (!contextName) contextName = config.context?.[0] ?? "default";
|
|
661
|
-
writeToContext(contextName, key, String(validated.data));
|
|
662
|
-
log("info", `Saved key=${key} to context ${contextName}`);
|
|
663
645
|
}
|
|
664
646
|
return { get, safeGet, save };
|
|
665
647
|
}
|
|
@@ -690,11 +672,6 @@ function parseScenvArgs(argv) {
|
|
|
690
672
|
config.set = config.set ?? {};
|
|
691
673
|
config.set[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
692
674
|
}
|
|
693
|
-
} else if (arg === "--save-prompt" && argv[i + 1] !== void 0) {
|
|
694
|
-
const v = argv[++i].toLowerCase();
|
|
695
|
-
if (["always", "never", "ask"].includes(v)) {
|
|
696
|
-
config.shouldSavePrompt = v;
|
|
697
|
-
}
|
|
698
675
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
699
676
|
config.saveContextTo = argv[++i];
|
|
700
677
|
} else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
|
|
@@ -728,10 +705,14 @@ function parseScenvArgs(argv) {
|
|
|
728
705
|
discoverContextPaths,
|
|
729
706
|
getCallbacks,
|
|
730
707
|
getContext,
|
|
708
|
+
getContextWritePath,
|
|
709
|
+
getInMemoryContext,
|
|
731
710
|
getMergedContextValues,
|
|
732
711
|
loadConfig,
|
|
733
712
|
parseScenvArgs,
|
|
734
713
|
resetConfig,
|
|
714
|
+
resetInMemoryContext,
|
|
735
715
|
resetLogState,
|
|
736
|
-
scenv
|
|
716
|
+
scenv,
|
|
717
|
+
setInMemoryContext
|
|
737
718
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -6,13 +6,6 @@
|
|
|
6
6
|
* - `"no-env"` – Prompt when the env var is not set (even if context has a value).
|
|
7
7
|
*/
|
|
8
8
|
type PromptMode = "always" | "never" | "fallback" | "no-env";
|
|
9
|
-
/**
|
|
10
|
-
* What to do with the value after the user was just prompted for it.
|
|
11
|
-
* - `"never"` – Do not save; discard for next time.
|
|
12
|
-
* - `"always"` – Save it (no prompt). Use saveContextTo or onAskContext only to pick where.
|
|
13
|
-
* - `"ask"` – Call {@link ScenvCallbacks.onAskWhetherToSave}; if true save, if false don't save.
|
|
14
|
-
*/
|
|
15
|
-
type SavePromptMode = "always" | "never" | "ask";
|
|
16
9
|
/**
|
|
17
10
|
* Valid log levels. Use with {@link ScenvConfig.logLevel}.
|
|
18
11
|
* Messages at or above the configured level are written to stderr.
|
|
@@ -38,10 +31,8 @@ interface ScenvConfig {
|
|
|
38
31
|
ignoreContext?: boolean;
|
|
39
32
|
/** Override values by key (CLI: `--set key=value`). Takes precedence over env and context. */
|
|
40
33
|
set?: Record<string, string>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/** Target context for saving: a context name, or `"ask"` to use {@link ScenvCallbacks.onAskContext}. */
|
|
44
|
-
saveContextTo?: "ask" | string;
|
|
34
|
+
/** 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
|
+
saveContextTo?: string;
|
|
45
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. */
|
|
46
37
|
contextDir?: string;
|
|
47
38
|
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
@@ -57,22 +48,16 @@ interface ScenvConfig {
|
|
|
57
48
|
*/
|
|
58
49
|
type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
|
|
59
50
|
/**
|
|
60
|
-
* Callbacks for interactive behaviour.
|
|
61
|
-
* Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
51
|
+
* Callbacks for interactive behaviour. Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
62
52
|
*/
|
|
63
53
|
interface ScenvCallbacks {
|
|
64
54
|
/** Used when a variable does not define its own `prompt`. Variable-level `prompt` overrides this. */
|
|
65
55
|
defaultPrompt?: DefaultPromptFn;
|
|
66
|
-
/** Called only when {@link ScenvConfig.shouldSavePrompt} is "ask" and the user was just prompted. Return true to save, false to skip. (With "always", we save without calling this.) */
|
|
67
|
-
onAskWhetherToSave?: (name: string, value: unknown) => Promise<boolean>;
|
|
68
|
-
/** Called when the save destination is ambiguous: saveContextTo is "ask", or after a prompt when the user said yes and saveContextTo is "ask". Return the context name to write to. */
|
|
69
|
-
onAskContext?: (name: string, contextNames: string[]) => Promise<string>;
|
|
70
56
|
}
|
|
71
57
|
/**
|
|
72
|
-
* Returns the current callbacks (with built-in
|
|
73
|
-
* Useful for inspection or to pass a subset to another layer. Each call returns a new object.
|
|
58
|
+
* Returns the current callbacks (with built-in default for defaultPrompt when not set).
|
|
74
59
|
*
|
|
75
|
-
* @returns Copy of the effective callbacks
|
|
60
|
+
* @returns Copy of the effective callbacks.
|
|
76
61
|
*/
|
|
77
62
|
declare function getCallbacks(): ScenvCallbacks;
|
|
78
63
|
/**
|
|
@@ -108,6 +93,19 @@ declare function resetConfig(): void;
|
|
|
108
93
|
*/
|
|
109
94
|
declare function resetLogState(): void;
|
|
110
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Returns the current in-memory context (key → value). Used during resolution before file contexts.
|
|
98
|
+
* Modifying the returned object mutates the store.
|
|
99
|
+
*/
|
|
100
|
+
declare function getInMemoryContext(): Record<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Sets a key-value pair in the in-memory context. Used when saving after prompt or save() when saveContextTo is unset, and always updated when a value is saved so the next get() sees it.
|
|
103
|
+
*/
|
|
104
|
+
declare function setInMemoryContext(key: string, value: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* Clears the in-memory context. Mainly for tests. Call in beforeEach to get a clean slate.
|
|
107
|
+
*/
|
|
108
|
+
declare function resetInMemoryContext(): void;
|
|
111
109
|
/**
|
|
112
110
|
* Recursively discovers all `*.context.json` files under a directory. Context name is the
|
|
113
111
|
* filename without the suffix (e.g. `dev.context.json` → "dev"). First file found for a
|
|
@@ -130,14 +128,23 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
130
128
|
*/
|
|
131
129
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
132
130
|
/**
|
|
133
|
-
* Loads and merges context values from the current config.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* resolution (set > env > context > default).
|
|
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.
|
|
134
|
+
* Used during variable resolution (set > env > in-memory > merged context > default).
|
|
137
135
|
*
|
|
138
136
|
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
139
137
|
*/
|
|
140
138
|
declare function getMergedContextValues(): Record<string, string>;
|
|
139
|
+
/**
|
|
140
|
+
* Returns the file path used for a context name or path when saving.
|
|
141
|
+
* - 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.
|
|
143
|
+
*
|
|
144
|
+
* @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
|
|
145
|
+
* @returns Absolute path to the context JSON file.
|
|
146
|
+
*/
|
|
147
|
+
declare function getContextWritePath(contextName: string): string;
|
|
141
148
|
|
|
142
149
|
/**
|
|
143
150
|
* Return type for a variable's optional `validator` function. Use a boolean for simple
|
|
@@ -205,8 +212,8 @@ interface ScenvVariable<T> {
|
|
|
205
212
|
error?: unknown;
|
|
206
213
|
}>;
|
|
207
214
|
/**
|
|
208
|
-
* Write the value to
|
|
209
|
-
* If you don't pass a value, the last resolved value is used. Does not prompt
|
|
215
|
+
* Write the value to the save target: if config.saveContextTo is set, to that context file; otherwise to in-memory only.
|
|
216
|
+
* If you don't pass a value, the last resolved value is used. Does not prompt; it saves. The value is always stored in-memory so the next get() sees it.
|
|
210
217
|
*/
|
|
211
218
|
save(value?: T): Promise<void>;
|
|
212
219
|
}
|
|
@@ -236,9 +243,9 @@ interface ScenvVariable<T> {
|
|
|
236
243
|
* ## save()
|
|
237
244
|
*
|
|
238
245
|
* The variable has a save(value?) method. It writes the value (or the last resolved value if you omit it)
|
|
239
|
-
* to
|
|
240
|
-
*
|
|
241
|
-
*
|
|
246
|
+
* to the save target (config.saveContextTo file if set, otherwise in-memory). save() does not ask; it saves.
|
|
247
|
+
* When the user is prompted during get(), the value is always saved (to saveContextTo file if set, and always to in-memory)
|
|
248
|
+
* so a second get() on the same variable does not prompt again.
|
|
242
249
|
*
|
|
243
250
|
* @typeParam T - Value type (default string). Use a validator to coerce to number, boolean, etc.
|
|
244
251
|
* @param name - Display name used in prompts and errors. If you omit key/env, key is derived from name (e.g. "API URL" → "api_url") and env from key (e.g. "API_URL").
|
|
@@ -262,8 +269,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
262
269
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
263
270
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
264
271
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
265
|
-
* - `--save-
|
|
266
|
-
* - `--save-context-to name` – saveContextTo.
|
|
272
|
+
* - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
|
|
267
273
|
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
268
274
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
269
275
|
*
|
|
@@ -272,4 +278,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
272
278
|
*/
|
|
273
279
|
declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
|
|
274
280
|
|
|
275
|
-
export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,13 +6,6 @@
|
|
|
6
6
|
* - `"no-env"` – Prompt when the env var is not set (even if context has a value).
|
|
7
7
|
*/
|
|
8
8
|
type PromptMode = "always" | "never" | "fallback" | "no-env";
|
|
9
|
-
/**
|
|
10
|
-
* What to do with the value after the user was just prompted for it.
|
|
11
|
-
* - `"never"` – Do not save; discard for next time.
|
|
12
|
-
* - `"always"` – Save it (no prompt). Use saveContextTo or onAskContext only to pick where.
|
|
13
|
-
* - `"ask"` – Call {@link ScenvCallbacks.onAskWhetherToSave}; if true save, if false don't save.
|
|
14
|
-
*/
|
|
15
|
-
type SavePromptMode = "always" | "never" | "ask";
|
|
16
9
|
/**
|
|
17
10
|
* Valid log levels. Use with {@link ScenvConfig.logLevel}.
|
|
18
11
|
* Messages at or above the configured level are written to stderr.
|
|
@@ -38,10 +31,8 @@ interface ScenvConfig {
|
|
|
38
31
|
ignoreContext?: boolean;
|
|
39
32
|
/** Override values by key (CLI: `--set key=value`). Takes precedence over env and context. */
|
|
40
33
|
set?: Record<string, string>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/** Target context for saving: a context name, or `"ask"` to use {@link ScenvCallbacks.onAskContext}. */
|
|
44
|
-
saveContextTo?: "ask" | string;
|
|
34
|
+
/** 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
|
+
saveContextTo?: string;
|
|
45
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. */
|
|
46
37
|
contextDir?: string;
|
|
47
38
|
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
@@ -57,22 +48,16 @@ interface ScenvConfig {
|
|
|
57
48
|
*/
|
|
58
49
|
type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
|
|
59
50
|
/**
|
|
60
|
-
* Callbacks for interactive behaviour.
|
|
61
|
-
* Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
51
|
+
* Callbacks for interactive behaviour. Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
62
52
|
*/
|
|
63
53
|
interface ScenvCallbacks {
|
|
64
54
|
/** Used when a variable does not define its own `prompt`. Variable-level `prompt` overrides this. */
|
|
65
55
|
defaultPrompt?: DefaultPromptFn;
|
|
66
|
-
/** Called only when {@link ScenvConfig.shouldSavePrompt} is "ask" and the user was just prompted. Return true to save, false to skip. (With "always", we save without calling this.) */
|
|
67
|
-
onAskWhetherToSave?: (name: string, value: unknown) => Promise<boolean>;
|
|
68
|
-
/** Called when the save destination is ambiguous: saveContextTo is "ask", or after a prompt when the user said yes and saveContextTo is "ask". Return the context name to write to. */
|
|
69
|
-
onAskContext?: (name: string, contextNames: string[]) => Promise<string>;
|
|
70
56
|
}
|
|
71
57
|
/**
|
|
72
|
-
* Returns the current callbacks (with built-in
|
|
73
|
-
* Useful for inspection or to pass a subset to another layer. Each call returns a new object.
|
|
58
|
+
* Returns the current callbacks (with built-in default for defaultPrompt when not set).
|
|
74
59
|
*
|
|
75
|
-
* @returns Copy of the effective callbacks
|
|
60
|
+
* @returns Copy of the effective callbacks.
|
|
76
61
|
*/
|
|
77
62
|
declare function getCallbacks(): ScenvCallbacks;
|
|
78
63
|
/**
|
|
@@ -108,6 +93,19 @@ declare function resetConfig(): void;
|
|
|
108
93
|
*/
|
|
109
94
|
declare function resetLogState(): void;
|
|
110
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Returns the current in-memory context (key → value). Used during resolution before file contexts.
|
|
98
|
+
* Modifying the returned object mutates the store.
|
|
99
|
+
*/
|
|
100
|
+
declare function getInMemoryContext(): Record<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Sets a key-value pair in the in-memory context. Used when saving after prompt or save() when saveContextTo is unset, and always updated when a value is saved so the next get() sees it.
|
|
103
|
+
*/
|
|
104
|
+
declare function setInMemoryContext(key: string, value: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* Clears the in-memory context. Mainly for tests. Call in beforeEach to get a clean slate.
|
|
107
|
+
*/
|
|
108
|
+
declare function resetInMemoryContext(): void;
|
|
111
109
|
/**
|
|
112
110
|
* Recursively discovers all `*.context.json` files under a directory. Context name is the
|
|
113
111
|
* filename without the suffix (e.g. `dev.context.json` → "dev"). First file found for a
|
|
@@ -130,14 +128,23 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
130
128
|
*/
|
|
131
129
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
132
130
|
/**
|
|
133
|
-
* Loads and merges context values from the current config.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* resolution (set > env > context > default).
|
|
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.
|
|
134
|
+
* Used during variable resolution (set > env > in-memory > merged context > default).
|
|
137
135
|
*
|
|
138
136
|
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
139
137
|
*/
|
|
140
138
|
declare function getMergedContextValues(): Record<string, string>;
|
|
139
|
+
/**
|
|
140
|
+
* Returns the file path used for a context name or path when saving.
|
|
141
|
+
* - 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.
|
|
143
|
+
*
|
|
144
|
+
* @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
|
|
145
|
+
* @returns Absolute path to the context JSON file.
|
|
146
|
+
*/
|
|
147
|
+
declare function getContextWritePath(contextName: string): string;
|
|
141
148
|
|
|
142
149
|
/**
|
|
143
150
|
* Return type for a variable's optional `validator` function. Use a boolean for simple
|
|
@@ -205,8 +212,8 @@ interface ScenvVariable<T> {
|
|
|
205
212
|
error?: unknown;
|
|
206
213
|
}>;
|
|
207
214
|
/**
|
|
208
|
-
* Write the value to
|
|
209
|
-
* If you don't pass a value, the last resolved value is used. Does not prompt
|
|
215
|
+
* Write the value to the save target: if config.saveContextTo is set, to that context file; otherwise to in-memory only.
|
|
216
|
+
* If you don't pass a value, the last resolved value is used. Does not prompt; it saves. The value is always stored in-memory so the next get() sees it.
|
|
210
217
|
*/
|
|
211
218
|
save(value?: T): Promise<void>;
|
|
212
219
|
}
|
|
@@ -236,9 +243,9 @@ interface ScenvVariable<T> {
|
|
|
236
243
|
* ## save()
|
|
237
244
|
*
|
|
238
245
|
* The variable has a save(value?) method. It writes the value (or the last resolved value if you omit it)
|
|
239
|
-
* to
|
|
240
|
-
*
|
|
241
|
-
*
|
|
246
|
+
* to the save target (config.saveContextTo file if set, otherwise in-memory). save() does not ask; it saves.
|
|
247
|
+
* When the user is prompted during get(), the value is always saved (to saveContextTo file if set, and always to in-memory)
|
|
248
|
+
* so a second get() on the same variable does not prompt again.
|
|
242
249
|
*
|
|
243
250
|
* @typeParam T - Value type (default string). Use a validator to coerce to number, boolean, etc.
|
|
244
251
|
* @param name - Display name used in prompts and errors. If you omit key/env, key is derived from name (e.g. "API URL" → "api_url") and env from key (e.g. "API_URL").
|
|
@@ -262,8 +269,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
262
269
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
263
270
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
264
271
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
265
|
-
* - `--save-
|
|
266
|
-
* - `--save-context-to name` – saveContextTo.
|
|
272
|
+
* - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
|
|
267
273
|
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
268
274
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
269
275
|
*
|
|
@@ -272,4 +278,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
272
278
|
*/
|
|
273
279
|
declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
|
|
274
280
|
|
|
275
|
-
export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -4,19 +4,6 @@ import { dirname, join } from "path";
|
|
|
4
4
|
|
|
5
5
|
// src/prompt-default.ts
|
|
6
6
|
import { createInterface } from "readline";
|
|
7
|
-
function ask(message) {
|
|
8
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
rl.question(message, (answer) => {
|
|
11
|
-
rl.close();
|
|
12
|
-
resolve(answer.trim());
|
|
13
|
-
});
|
|
14
|
-
rl.on("error", (err) => {
|
|
15
|
-
rl.close();
|
|
16
|
-
reject(err);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
7
|
function defaultPrompt(name, defaultValue) {
|
|
21
8
|
const defaultStr = defaultValue !== void 0 && defaultValue !== null ? String(defaultValue) : "";
|
|
22
9
|
const message = defaultStr ? `Enter ${name} [${defaultStr}]: ` : `Enter ${name}: `;
|
|
@@ -34,17 +21,6 @@ function defaultPrompt(name, defaultValue) {
|
|
|
34
21
|
});
|
|
35
22
|
});
|
|
36
23
|
}
|
|
37
|
-
async function defaultAskWhetherToSave(name, _value) {
|
|
38
|
-
const answer = await ask(`Save "${name}" for next time? (y/n): `);
|
|
39
|
-
const v = answer.toLowerCase();
|
|
40
|
-
return v === "y" || v === "yes" || v === "1" || v === "true";
|
|
41
|
-
}
|
|
42
|
-
async function defaultAskContext(name, contextNames) {
|
|
43
|
-
const hint = contextNames.length > 0 ? ` (${contextNames.join(", ")})` : "";
|
|
44
|
-
const answer = await ask(`Save "${name}" to which context?${hint}: `);
|
|
45
|
-
if (answer) return answer;
|
|
46
|
-
return contextNames[0] ?? "default";
|
|
47
|
-
}
|
|
48
24
|
|
|
49
25
|
// src/config.ts
|
|
50
26
|
var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
|
|
@@ -55,7 +31,6 @@ var envKeyMap = {
|
|
|
55
31
|
SCENV_PROMPT: "prompt",
|
|
56
32
|
SCENV_IGNORE_ENV: "ignoreEnv",
|
|
57
33
|
SCENV_IGNORE_CONTEXT: "ignoreContext",
|
|
58
|
-
SCENV_SAVE_PROMPT: "shouldSavePrompt",
|
|
59
34
|
SCENV_SAVE_CONTEXT_TO: "saveContextTo",
|
|
60
35
|
SCENV_CONTEXT_DIR: "contextDir",
|
|
61
36
|
SCENV_LOG_LEVEL: "logLevel"
|
|
@@ -64,9 +39,7 @@ var programmaticConfig = {};
|
|
|
64
39
|
var programmaticCallbacks = {};
|
|
65
40
|
function getCallbacks() {
|
|
66
41
|
return {
|
|
67
|
-
defaultPrompt: programmaticCallbacks.defaultPrompt ?? defaultPrompt
|
|
68
|
-
onAskWhetherToSave: programmaticCallbacks.onAskWhetherToSave ?? defaultAskWhetherToSave,
|
|
69
|
-
onAskContext: programmaticCallbacks.onAskContext ?? defaultAskContext
|
|
42
|
+
defaultPrompt: programmaticCallbacks.defaultPrompt ?? defaultPrompt
|
|
70
43
|
};
|
|
71
44
|
}
|
|
72
45
|
function findConfigDir(startDir) {
|
|
@@ -89,11 +62,9 @@ function configFromEnv() {
|
|
|
89
62
|
out[configKey] = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
90
63
|
} else if (configKey === "ignoreEnv" || configKey === "ignoreContext") {
|
|
91
64
|
out[configKey] = val === "1" || val === "true" || val.toLowerCase() === "yes";
|
|
92
|
-
} else if (configKey === "prompt"
|
|
65
|
+
} else if (configKey === "prompt") {
|
|
93
66
|
const v = val.toLowerCase();
|
|
94
|
-
if (
|
|
95
|
-
out[configKey] = v;
|
|
96
|
-
if (configKey === "shouldSavePrompt" && (v === "always" || v === "never" || v === "ask"))
|
|
67
|
+
if (v === "always" || v === "never" || v === "fallback" || v === "no-env")
|
|
97
68
|
out[configKey] = v;
|
|
98
69
|
} else if (configKey === "saveContextTo") {
|
|
99
70
|
out.saveContextTo = val;
|
|
@@ -136,8 +107,6 @@ function loadConfigFile(configDir) {
|
|
|
136
107
|
out.ignoreContext = parsed.ignoreContext;
|
|
137
108
|
if (parsed.set && typeof parsed.set === "object" && !Array.isArray(parsed.set))
|
|
138
109
|
out.set = parsed.set;
|
|
139
|
-
if (typeof (parsed.shouldSavePrompt ?? parsed.savePrompt) === "string" && ["always", "never", "ask"].includes(parsed.shouldSavePrompt ?? parsed.savePrompt))
|
|
140
|
-
out.shouldSavePrompt = parsed.shouldSavePrompt ?? parsed.savePrompt;
|
|
141
110
|
if (typeof parsed.saveContextTo === "string")
|
|
142
111
|
out.saveContextTo = parsed.saveContextTo;
|
|
143
112
|
if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
|
|
@@ -228,7 +197,6 @@ var CONFIG_LOG_KEYS = [
|
|
|
228
197
|
"ignoreEnv",
|
|
229
198
|
"ignoreContext",
|
|
230
199
|
"set",
|
|
231
|
-
"shouldSavePrompt",
|
|
232
200
|
"saveContextTo",
|
|
233
201
|
"contextDir",
|
|
234
202
|
"logLevel"
|
|
@@ -252,7 +220,6 @@ function logConfigLoaded(config) {
|
|
|
252
220
|
if (config.ignoreContext === true) parts.push("ignoreContext=true");
|
|
253
221
|
if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
|
|
254
222
|
if (config.contextDir !== void 0) parts.push("contextDir=" + config.contextDir);
|
|
255
|
-
if (config.shouldSavePrompt !== void 0) parts.push("shouldSavePrompt=" + config.shouldSavePrompt);
|
|
256
223
|
log("info", "config loaded", parts.join(" "));
|
|
257
224
|
}
|
|
258
225
|
if (levelNum >= LEVEL_NUM.debug) {
|
|
@@ -277,10 +244,21 @@ import {
|
|
|
277
244
|
writeFileSync,
|
|
278
245
|
mkdirSync,
|
|
279
246
|
readdirSync,
|
|
280
|
-
statSync
|
|
247
|
+
statSync,
|
|
248
|
+
existsSync as existsSync2
|
|
281
249
|
} from "fs";
|
|
282
|
-
import { join as join2, dirname as dirname2, isAbsolute } from "path";
|
|
250
|
+
import { join as join2, dirname as dirname2, isAbsolute, sep } from "path";
|
|
283
251
|
var CONTEXT_SUFFIX = ".context.json";
|
|
252
|
+
var inMemoryContext = {};
|
|
253
|
+
function getInMemoryContext() {
|
|
254
|
+
return inMemoryContext;
|
|
255
|
+
}
|
|
256
|
+
function setInMemoryContext(key, value) {
|
|
257
|
+
inMemoryContext[key] = value;
|
|
258
|
+
}
|
|
259
|
+
function resetInMemoryContext() {
|
|
260
|
+
inMemoryContext = {};
|
|
261
|
+
}
|
|
284
262
|
function discoverContextPathsInternal(dir, found) {
|
|
285
263
|
let entries;
|
|
286
264
|
try {
|
|
@@ -348,6 +326,14 @@ function getMergedContextValues() {
|
|
|
348
326
|
const root = config.root ?? process.cwd();
|
|
349
327
|
const paths = discoverContextPaths(root);
|
|
350
328
|
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
|
+
}
|
|
351
337
|
for (const contextName of config.context ?? []) {
|
|
352
338
|
const filePath = paths.get(contextName);
|
|
353
339
|
if (!filePath) {
|
|
@@ -375,6 +361,9 @@ function getMergedContextValues() {
|
|
|
375
361
|
return out;
|
|
376
362
|
}
|
|
377
363
|
function getContextWritePath(contextName) {
|
|
364
|
+
if (isAbsolute(contextName) || contextName.includes(sep)) {
|
|
365
|
+
return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
|
|
366
|
+
}
|
|
378
367
|
const config = loadConfig();
|
|
379
368
|
const root = config.root ?? process.cwd();
|
|
380
369
|
const paths = discoverContextPaths(root);
|
|
@@ -383,6 +372,26 @@ function getContextWritePath(contextName) {
|
|
|
383
372
|
const saveDir = config.contextDir ? isAbsolute(config.contextDir) ? config.contextDir : join2(root, config.contextDir) : root;
|
|
384
373
|
return join2(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
|
|
385
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
|
+
}
|
|
394
|
+
}
|
|
386
395
|
function writeToContext(contextName, key, value) {
|
|
387
396
|
const path = getContextWritePath(contextName);
|
|
388
397
|
let data = {};
|
|
@@ -479,6 +488,12 @@ function scenv(name, options = {}) {
|
|
|
479
488
|
return { raw, source: "env" };
|
|
480
489
|
}
|
|
481
490
|
}
|
|
491
|
+
const mem = getInMemoryContext();
|
|
492
|
+
if (mem[key] !== void 0) {
|
|
493
|
+
log("trace", `resolveRaw: in-memory hit key=${key}`);
|
|
494
|
+
const raw = resolveContextReference(mem[key], key);
|
|
495
|
+
return { raw, source: "context" };
|
|
496
|
+
}
|
|
482
497
|
if (!config.ignoreContext) {
|
|
483
498
|
log("trace", "resolveRaw: checking context");
|
|
484
499
|
const ctx = getMergedContextValues();
|
|
@@ -564,36 +579,11 @@ function scenv(name, options = {}) {
|
|
|
564
579
|
const final = validated.data;
|
|
565
580
|
if (wasPrompted) {
|
|
566
581
|
const config = loadConfig();
|
|
567
|
-
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const callbacks2 = getCallbacks();
|
|
572
|
-
if (typeof callbacks2.onAskWhetherToSave !== "function") {
|
|
573
|
-
throw new Error(
|
|
574
|
-
`shouldSavePrompt is "ask" but onAskWhetherToSave callback is not set. Configure callbacks via configure({ callbacks: { onAskWhetherToSave: ... } }).`
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
doSave = await callbacks2.onAskWhetherToSave(name, final);
|
|
578
|
-
} else {
|
|
579
|
-
doSave = true;
|
|
580
|
-
}
|
|
581
|
-
if (!doSave) return final;
|
|
582
|
-
const callbacks = getCallbacks();
|
|
583
|
-
const contextNames = config.context ?? [];
|
|
584
|
-
let ctxToSave;
|
|
585
|
-
if (config.saveContextTo === "ask") {
|
|
586
|
-
if (typeof callbacks.onAskContext !== "function") {
|
|
587
|
-
throw new Error(
|
|
588
|
-
`saveContextTo is "ask" but onAskContext callback is not set. Configure callbacks via configure({ callbacks: { onAskContext: ... } }).`
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
ctxToSave = await callbacks.onAskContext(name, contextNames);
|
|
592
|
-
} else {
|
|
593
|
-
ctxToSave = config.saveContextTo ?? contextNames[0] ?? "default";
|
|
582
|
+
setInMemoryContext(key, String(final));
|
|
583
|
+
if (config.saveContextTo) {
|
|
584
|
+
writeToContext(config.saveContextTo, key, String(final));
|
|
585
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
594
586
|
}
|
|
595
|
-
writeToContext(ctxToSave, key, String(final));
|
|
596
|
-
log("info", `Saved key=${key} to context ${ctxToSave}`);
|
|
597
587
|
}
|
|
598
588
|
return final;
|
|
599
589
|
}
|
|
@@ -614,22 +604,11 @@ function scenv(name, options = {}) {
|
|
|
614
604
|
throw new Error(errMsg);
|
|
615
605
|
}
|
|
616
606
|
const config = loadConfig();
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
throw new Error(
|
|
622
|
-
`saveContextTo is "ask" but onAskContext callback is not set. Configure callbacks via configure({ callbacks: { onAskContext: ... } }).`
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
contextName = await callbacks.onAskContext(
|
|
626
|
-
name,
|
|
627
|
-
config.context ?? []
|
|
628
|
-
);
|
|
607
|
+
setInMemoryContext(key, String(validated.data));
|
|
608
|
+
if (config.saveContextTo) {
|
|
609
|
+
writeToContext(config.saveContextTo, key, String(validated.data));
|
|
610
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
629
611
|
}
|
|
630
|
-
if (!contextName) contextName = config.context?.[0] ?? "default";
|
|
631
|
-
writeToContext(contextName, key, String(validated.data));
|
|
632
|
-
log("info", `Saved key=${key} to context ${contextName}`);
|
|
633
612
|
}
|
|
634
613
|
return { get, safeGet, save };
|
|
635
614
|
}
|
|
@@ -660,11 +639,6 @@ function parseScenvArgs(argv) {
|
|
|
660
639
|
config.set = config.set ?? {};
|
|
661
640
|
config.set[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
662
641
|
}
|
|
663
|
-
} else if (arg === "--save-prompt" && argv[i + 1] !== void 0) {
|
|
664
|
-
const v = argv[++i].toLowerCase();
|
|
665
|
-
if (["always", "never", "ask"].includes(v)) {
|
|
666
|
-
config.shouldSavePrompt = v;
|
|
667
|
-
}
|
|
668
642
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
669
643
|
config.saveContextTo = argv[++i];
|
|
670
644
|
} else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
|
|
@@ -697,10 +671,14 @@ export {
|
|
|
697
671
|
discoverContextPaths,
|
|
698
672
|
getCallbacks,
|
|
699
673
|
getContext,
|
|
674
|
+
getContextWritePath,
|
|
675
|
+
getInMemoryContext,
|
|
700
676
|
getMergedContextValues,
|
|
701
677
|
loadConfig,
|
|
702
678
|
parseScenvArgs,
|
|
703
679
|
resetConfig,
|
|
680
|
+
resetInMemoryContext,
|
|
704
681
|
resetLogState,
|
|
705
|
-
scenv
|
|
682
|
+
scenv,
|
|
683
|
+
setInMemoryContext
|
|
706
684
|
};
|