scenv 0.6.1 → 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 +85 -111
- package/dist/index.d.cts +52 -38
- package/dist/index.d.ts +52 -38
- package/dist/index.js +83 -112
- 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,29 +44,16 @@ 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}: `;
|
|
59
50
|
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
60
|
-
return new Promise((
|
|
51
|
+
return new Promise((resolve2, reject) => {
|
|
61
52
|
rl.question(message, (answer) => {
|
|
62
53
|
rl.close();
|
|
63
54
|
const trimmed = answer.trim();
|
|
64
55
|
const value = trimmed !== "" ? trimmed : defaultStr;
|
|
65
|
-
|
|
56
|
+
resolve2(value);
|
|
66
57
|
});
|
|
67
58
|
rl.on("error", (err) => {
|
|
68
59
|
rl.close();
|
|
@@ -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,18 +71,15 @@ 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_SAVE_MODE: "saveMode",
|
|
97
76
|
SCENV_LOG_LEVEL: "logLevel"
|
|
98
77
|
};
|
|
99
78
|
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,16 +102,16 @@ 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;
|
|
136
|
-
} else if (configKey === "
|
|
137
|
-
|
|
111
|
+
} else if (configKey === "saveMode") {
|
|
112
|
+
const v = val.toLowerCase();
|
|
113
|
+
if (v === "all" || v === "prompts-only")
|
|
114
|
+
out[configKey] = v;
|
|
138
115
|
} else if (configKey === "logLevel") {
|
|
139
116
|
const v = val.toLowerCase();
|
|
140
117
|
if (LOG_LEVELS.includes(v)) out.logLevel = v;
|
|
@@ -172,12 +149,10 @@ function loadConfigFile(configDir) {
|
|
|
172
149
|
out.ignoreContext = parsed.ignoreContext;
|
|
173
150
|
if (parsed.set && typeof parsed.set === "object" && !Array.isArray(parsed.set))
|
|
174
151
|
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
152
|
if (typeof parsed.saveContextTo === "string")
|
|
178
153
|
out.saveContextTo = parsed.saveContextTo;
|
|
179
|
-
if (typeof parsed.
|
|
180
|
-
|
|
154
|
+
if (typeof parsed.saveMode === "string" && ["all", "prompts-only"].includes(parsed.saveMode))
|
|
155
|
+
out.saveMode = parsed.saveMode;
|
|
181
156
|
if (typeof parsed.root === "string") out.root = parsed.root;
|
|
182
157
|
if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
|
|
183
158
|
out.logLevel = parsed.logLevel;
|
|
@@ -264,9 +239,8 @@ var CONFIG_LOG_KEYS = [
|
|
|
264
239
|
"ignoreEnv",
|
|
265
240
|
"ignoreContext",
|
|
266
241
|
"set",
|
|
267
|
-
"shouldSavePrompt",
|
|
268
242
|
"saveContextTo",
|
|
269
|
-
"
|
|
243
|
+
"saveMode",
|
|
270
244
|
"logLevel"
|
|
271
245
|
];
|
|
272
246
|
function configForLog(config) {
|
|
@@ -287,8 +261,7 @@ function logConfigLoaded(config) {
|
|
|
287
261
|
if (config.ignoreEnv === true) parts.push("ignoreEnv=true");
|
|
288
262
|
if (config.ignoreContext === true) parts.push("ignoreContext=true");
|
|
289
263
|
if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
|
|
290
|
-
if (config.
|
|
291
|
-
if (config.shouldSavePrompt !== void 0) parts.push("shouldSavePrompt=" + config.shouldSavePrompt);
|
|
264
|
+
if (config.saveMode !== void 0) parts.push("saveMode=" + config.saveMode);
|
|
292
265
|
log("info", "config loaded", parts.join(" "));
|
|
293
266
|
}
|
|
294
267
|
if (levelNum >= LEVEL_NUM.debug) {
|
|
@@ -311,6 +284,16 @@ function log(level, msg, ...args) {
|
|
|
311
284
|
var import_node_fs2 = require("fs");
|
|
312
285
|
var import_node_path2 = require("path");
|
|
313
286
|
var CONTEXT_SUFFIX = ".context.json";
|
|
287
|
+
var inMemoryContext = {};
|
|
288
|
+
function getInMemoryContext() {
|
|
289
|
+
return inMemoryContext;
|
|
290
|
+
}
|
|
291
|
+
function setInMemoryContext(key, value) {
|
|
292
|
+
inMemoryContext[key] = value;
|
|
293
|
+
}
|
|
294
|
+
function resetInMemoryContext() {
|
|
295
|
+
inMemoryContext = {};
|
|
296
|
+
}
|
|
314
297
|
function discoverContextPathsInternal(dir, found) {
|
|
315
298
|
let entries;
|
|
316
299
|
try {
|
|
@@ -347,10 +330,23 @@ function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
|
|
|
347
330
|
return found;
|
|
348
331
|
}
|
|
349
332
|
function getContext(contextName, root) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
350
|
if (!filePath) {
|
|
355
351
|
log("trace", `getContext: context "${contextName}" not found`);
|
|
356
352
|
return {};
|
|
@@ -375,8 +371,8 @@ function getMergedContextValues() {
|
|
|
375
371
|
const config = loadConfig();
|
|
376
372
|
logConfigLoaded(config);
|
|
377
373
|
if (config.ignoreContext) return {};
|
|
378
|
-
const
|
|
379
|
-
const paths = discoverContextPaths(
|
|
374
|
+
const searchRoot = process.cwd();
|
|
375
|
+
const paths = discoverContextPaths(searchRoot);
|
|
380
376
|
const out = {};
|
|
381
377
|
for (const contextName of config.context ?? []) {
|
|
382
378
|
const filePath = paths.get(contextName);
|
|
@@ -405,13 +401,15 @@ function getMergedContextValues() {
|
|
|
405
401
|
return out;
|
|
406
402
|
}
|
|
407
403
|
function getContextWritePath(contextName) {
|
|
404
|
+
if ((0, import_node_path2.isAbsolute)(contextName) || contextName.includes(import_node_path2.sep)) {
|
|
405
|
+
return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
|
|
406
|
+
}
|
|
408
407
|
const config = loadConfig();
|
|
409
|
-
const
|
|
410
|
-
const paths = discoverContextPaths(root);
|
|
408
|
+
const paths = discoverContextPaths(process.cwd());
|
|
411
409
|
const existing = paths.get(contextName);
|
|
412
410
|
if (existing) return existing;
|
|
413
|
-
const
|
|
414
|
-
return (0, import_node_path2.join)(
|
|
411
|
+
const projectRoot = config.root ?? process.cwd();
|
|
412
|
+
return (0, import_node_path2.join)(projectRoot, `${contextName}${CONTEXT_SUFFIX}`);
|
|
415
413
|
}
|
|
416
414
|
function writeToContext(contextName, key, value) {
|
|
417
415
|
const path = getContextWritePath(contextName);
|
|
@@ -509,6 +507,12 @@ function scenv(name, options = {}) {
|
|
|
509
507
|
return { raw, source: "env" };
|
|
510
508
|
}
|
|
511
509
|
}
|
|
510
|
+
const mem = getInMemoryContext();
|
|
511
|
+
if (mem[key] !== void 0) {
|
|
512
|
+
log("trace", `resolveRaw: in-memory hit key=${key}`);
|
|
513
|
+
const raw = resolveContextReference(mem[key], key);
|
|
514
|
+
return { raw, source: "context" };
|
|
515
|
+
}
|
|
512
516
|
if (!config.ignoreContext) {
|
|
513
517
|
log("trace", "resolveRaw: checking context");
|
|
514
518
|
const ctx = getMergedContextValues();
|
|
@@ -592,38 +596,14 @@ function scenv(name, options = {}) {
|
|
|
592
596
|
throw new Error(errMsg);
|
|
593
597
|
}
|
|
594
598
|
const final = validated.data;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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";
|
|
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) {
|
|
604
|
+
writeToContext(config.saveContextTo, key, String(final));
|
|
605
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
624
606
|
}
|
|
625
|
-
writeToContext(ctxToSave, key, String(final));
|
|
626
|
-
log("info", `Saved key=${key} to context ${ctxToSave}`);
|
|
627
607
|
}
|
|
628
608
|
return final;
|
|
629
609
|
}
|
|
@@ -644,33 +624,25 @@ function scenv(name, options = {}) {
|
|
|
644
624
|
throw new Error(errMsg);
|
|
645
625
|
}
|
|
646
626
|
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
|
-
);
|
|
627
|
+
setInMemoryContext(key, String(validated.data));
|
|
628
|
+
if (config.saveContextTo) {
|
|
629
|
+
writeToContext(config.saveContextTo, key, String(validated.data));
|
|
630
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
659
631
|
}
|
|
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
632
|
}
|
|
664
633
|
return { get, safeGet, save };
|
|
665
634
|
}
|
|
666
635
|
|
|
667
636
|
// src/cli-args.ts
|
|
637
|
+
var import_node_path3 = require("path");
|
|
668
638
|
function parseScenvArgs(argv) {
|
|
669
639
|
const config = {};
|
|
670
640
|
let i = 0;
|
|
671
641
|
while (i < argv.length) {
|
|
672
642
|
const arg = argv[i];
|
|
673
|
-
if (arg === "--
|
|
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) {
|
|
674
646
|
config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
675
647
|
} else if (arg === "--add-context" && argv[i + 1] !== void 0) {
|
|
676
648
|
config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -690,15 +662,13 @@ function parseScenvArgs(argv) {
|
|
|
690
662
|
config.set = config.set ?? {};
|
|
691
663
|
config.set[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
692
664
|
}
|
|
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
665
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
699
666
|
config.saveContextTo = argv[++i];
|
|
700
|
-
} else if (arg === "--
|
|
701
|
-
|
|
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
|
+
}
|
|
702
672
|
} else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
|
|
703
673
|
const v = argv[++i].toLowerCase();
|
|
704
674
|
if (LOG_LEVELS.includes(v)) {
|
|
@@ -728,10 +698,14 @@ function parseScenvArgs(argv) {
|
|
|
728
698
|
discoverContextPaths,
|
|
729
699
|
getCallbacks,
|
|
730
700
|
getContext,
|
|
701
|
+
getContextWritePath,
|
|
702
|
+
getInMemoryContext,
|
|
731
703
|
getMergedContextValues,
|
|
732
704
|
loadConfig,
|
|
733
705
|
parseScenvArgs,
|
|
734
706
|
resetConfig,
|
|
707
|
+
resetInMemoryContext,
|
|
735
708
|
resetLogState,
|
|
736
|
-
scenv
|
|
709
|
+
scenv,
|
|
710
|
+
setInMemoryContext
|
|
737
711
|
});
|
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.
|
|
@@ -20,6 +13,12 @@ type SavePromptMode = "always" | "never" | "ask";
|
|
|
20
13
|
declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
|
|
21
14
|
/** Log level type. `"none"` disables logging; higher values are more verbose. */
|
|
22
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";
|
|
23
22
|
/**
|
|
24
23
|
* Full scenv configuration. Built from file (scenv.config.json), environment (SCENV_*),
|
|
25
24
|
* and programmatic config (configure()), with programmatic > env > file precedence.
|
|
@@ -38,13 +37,11 @@ interface ScenvConfig {
|
|
|
38
37
|
ignoreContext?: boolean;
|
|
39
38
|
/** Override values by key (CLI: `--set key=value`). Takes precedence over env and context. */
|
|
40
39
|
set?: Record<string, string>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
contextDir?: string;
|
|
47
|
-
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
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). */
|
|
41
|
+
saveContextTo?: string;
|
|
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. */
|
|
48
45
|
root?: string;
|
|
49
46
|
/** Logging level. Default is `"none"`. Messages go to stderr. */
|
|
50
47
|
logLevel?: LogLevel;
|
|
@@ -57,22 +54,16 @@ interface ScenvConfig {
|
|
|
57
54
|
*/
|
|
58
55
|
type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
|
|
59
56
|
/**
|
|
60
|
-
* Callbacks for interactive behaviour.
|
|
61
|
-
* Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
57
|
+
* Callbacks for interactive behaviour. Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
62
58
|
*/
|
|
63
59
|
interface ScenvCallbacks {
|
|
64
60
|
/** Used when a variable does not define its own `prompt`. Variable-level `prompt` overrides this. */
|
|
65
61
|
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
62
|
}
|
|
71
63
|
/**
|
|
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.
|
|
64
|
+
* Returns the current callbacks (with built-in default for defaultPrompt when not set).
|
|
74
65
|
*
|
|
75
|
-
* @returns Copy of the effective callbacks
|
|
66
|
+
* @returns Copy of the effective callbacks.
|
|
76
67
|
*/
|
|
77
68
|
declare function getCallbacks(): ScenvCallbacks;
|
|
78
69
|
/**
|
|
@@ -108,6 +99,19 @@ declare function resetConfig(): void;
|
|
|
108
99
|
*/
|
|
109
100
|
declare function resetLogState(): void;
|
|
110
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Returns the current in-memory context (key → value). Used during resolution before file contexts.
|
|
104
|
+
* Modifying the returned object mutates the store.
|
|
105
|
+
*/
|
|
106
|
+
declare function getInMemoryContext(): Record<string, string>;
|
|
107
|
+
/**
|
|
108
|
+
* 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.
|
|
109
|
+
*/
|
|
110
|
+
declare function setInMemoryContext(key: string, value: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Clears the in-memory context. Mainly for tests. Call in beforeEach to get a clean slate.
|
|
113
|
+
*/
|
|
114
|
+
declare function resetInMemoryContext(): void;
|
|
111
115
|
/**
|
|
112
116
|
* Recursively discovers all `*.context.json` files under a directory. Context name is the
|
|
113
117
|
* filename without the suffix (e.g. `dev.context.json` → "dev"). First file found for a
|
|
@@ -122,22 +126,32 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
122
126
|
/**
|
|
123
127
|
* Loads key-value pairs from a single context file. Used when resolving @context:key references.
|
|
124
128
|
* Does not depend on config.context or ignoreContext; the context file is read if it exists
|
|
125
|
-
* under the
|
|
129
|
+
* under the search directory.
|
|
126
130
|
*
|
|
127
131
|
* @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
|
|
128
|
-
* @param root - Optional
|
|
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.
|
|
129
133
|
* @returns A flat record of key → string value from that context file. Empty if file not found or invalid.
|
|
130
134
|
*/
|
|
131
135
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
132
136
|
/**
|
|
133
|
-
* Loads and merges context values from the current config
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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.
|
|
141
|
+
* Used during variable resolution (set > env > in-memory > merged context > default).
|
|
137
142
|
*
|
|
138
143
|
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
139
144
|
*/
|
|
140
145
|
declare function getMergedContextValues(): Record<string, string>;
|
|
146
|
+
/**
|
|
147
|
+
* Returns the file path used for a context name or path when saving.
|
|
148
|
+
* - If contextName is path-like (absolute or contains path separator), returns that path with .context.json appended if not already present.
|
|
149
|
+
* - Otherwise, if that context was already discovered under cwd, returns its path; else saves under project root (config.root or cwd).
|
|
150
|
+
*
|
|
151
|
+
* @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
|
|
152
|
+
* @returns Absolute path to the context JSON file.
|
|
153
|
+
*/
|
|
154
|
+
declare function getContextWritePath(contextName: string): string;
|
|
141
155
|
|
|
142
156
|
/**
|
|
143
157
|
* Return type for a variable's optional `validator` function. Use a boolean for simple
|
|
@@ -205,8 +219,8 @@ interface ScenvVariable<T> {
|
|
|
205
219
|
error?: unknown;
|
|
206
220
|
}>;
|
|
207
221
|
/**
|
|
208
|
-
* Write the value to
|
|
209
|
-
* If you don't pass a value, the last resolved value is used. Does not prompt
|
|
222
|
+
* Write the value to the save target: if config.saveContextTo is set, to that context file; otherwise to in-memory only.
|
|
223
|
+
* 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
224
|
*/
|
|
211
225
|
save(value?: T): Promise<void>;
|
|
212
226
|
}
|
|
@@ -236,9 +250,9 @@ interface ScenvVariable<T> {
|
|
|
236
250
|
* ## save()
|
|
237
251
|
*
|
|
238
252
|
* The variable has a save(value?) method. It writes the value (or the last resolved value if you omit it)
|
|
239
|
-
* to
|
|
240
|
-
*
|
|
241
|
-
*
|
|
253
|
+
* to the save target (config.saveContextTo file if set, otherwise in-memory). save() does not ask; it saves.
|
|
254
|
+
* When the user is prompted during get(), the value is always saved (to saveContextTo file if set, and always to in-memory)
|
|
255
|
+
* so a second get() on the same variable does not prompt again.
|
|
242
256
|
*
|
|
243
257
|
* @typeParam T - Value type (default string). Use a validator to coerce to number, boolean, etc.
|
|
244
258
|
* @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").
|
|
@@ -256,15 +270,15 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
256
270
|
* Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
|
|
257
271
|
*
|
|
258
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.
|
|
259
274
|
* - `--context a,b,c` – Set context list (replace).
|
|
260
275
|
* - `--add-context x,y` – Add context names.
|
|
261
276
|
* - `--prompt always|never|fallback|no-env` – Prompt mode.
|
|
262
277
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
263
278
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
264
279
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
265
|
-
* - `--save-
|
|
266
|
-
* - `--save-
|
|
267
|
-
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
280
|
+
* - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
|
|
281
|
+
* - `--save-mode all|prompts-only` – When to write to saveContextTo during get(); default is all.
|
|
268
282
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
269
283
|
*
|
|
270
284
|
* @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
|
|
@@ -272,4 +286,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
272
286
|
*/
|
|
273
287
|
declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
|
|
274
288
|
|
|
275
|
-
export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type
|
|
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
|
@@ -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.
|
|
@@ -20,6 +13,12 @@ type SavePromptMode = "always" | "never" | "ask";
|
|
|
20
13
|
declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
|
|
21
14
|
/** Log level type. `"none"` disables logging; higher values are more verbose. */
|
|
22
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";
|
|
23
22
|
/**
|
|
24
23
|
* Full scenv configuration. Built from file (scenv.config.json), environment (SCENV_*),
|
|
25
24
|
* and programmatic config (configure()), with programmatic > env > file precedence.
|
|
@@ -38,13 +37,11 @@ interface ScenvConfig {
|
|
|
38
37
|
ignoreContext?: boolean;
|
|
39
38
|
/** Override values by key (CLI: `--set key=value`). Takes precedence over env and context. */
|
|
40
39
|
set?: Record<string, string>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
contextDir?: string;
|
|
47
|
-
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
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). */
|
|
41
|
+
saveContextTo?: string;
|
|
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. */
|
|
48
45
|
root?: string;
|
|
49
46
|
/** Logging level. Default is `"none"`. Messages go to stderr. */
|
|
50
47
|
logLevel?: LogLevel;
|
|
@@ -57,22 +54,16 @@ interface ScenvConfig {
|
|
|
57
54
|
*/
|
|
58
55
|
type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
|
|
59
56
|
/**
|
|
60
|
-
* Callbacks for interactive behaviour.
|
|
61
|
-
* Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
57
|
+
* Callbacks for interactive behaviour. Pass to {@link configure} via `configure({ callbacks: { ... } })`.
|
|
62
58
|
*/
|
|
63
59
|
interface ScenvCallbacks {
|
|
64
60
|
/** Used when a variable does not define its own `prompt`. Variable-level `prompt` overrides this. */
|
|
65
61
|
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
62
|
}
|
|
71
63
|
/**
|
|
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.
|
|
64
|
+
* Returns the current callbacks (with built-in default for defaultPrompt when not set).
|
|
74
65
|
*
|
|
75
|
-
* @returns Copy of the effective callbacks
|
|
66
|
+
* @returns Copy of the effective callbacks.
|
|
76
67
|
*/
|
|
77
68
|
declare function getCallbacks(): ScenvCallbacks;
|
|
78
69
|
/**
|
|
@@ -108,6 +99,19 @@ declare function resetConfig(): void;
|
|
|
108
99
|
*/
|
|
109
100
|
declare function resetLogState(): void;
|
|
110
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Returns the current in-memory context (key → value). Used during resolution before file contexts.
|
|
104
|
+
* Modifying the returned object mutates the store.
|
|
105
|
+
*/
|
|
106
|
+
declare function getInMemoryContext(): Record<string, string>;
|
|
107
|
+
/**
|
|
108
|
+
* 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.
|
|
109
|
+
*/
|
|
110
|
+
declare function setInMemoryContext(key: string, value: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Clears the in-memory context. Mainly for tests. Call in beforeEach to get a clean slate.
|
|
113
|
+
*/
|
|
114
|
+
declare function resetInMemoryContext(): void;
|
|
111
115
|
/**
|
|
112
116
|
* Recursively discovers all `*.context.json` files under a directory. Context name is the
|
|
113
117
|
* filename without the suffix (e.g. `dev.context.json` → "dev"). First file found for a
|
|
@@ -122,22 +126,32 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
122
126
|
/**
|
|
123
127
|
* Loads key-value pairs from a single context file. Used when resolving @context:key references.
|
|
124
128
|
* Does not depend on config.context or ignoreContext; the context file is read if it exists
|
|
125
|
-
* under the
|
|
129
|
+
* under the search directory.
|
|
126
130
|
*
|
|
127
131
|
* @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
|
|
128
|
-
* @param root - Optional
|
|
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.
|
|
129
133
|
* @returns A flat record of key → string value from that context file. Empty if file not found or invalid.
|
|
130
134
|
*/
|
|
131
135
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
132
136
|
/**
|
|
133
|
-
* Loads and merges context values from the current config
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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.
|
|
141
|
+
* Used during variable resolution (set > env > in-memory > merged context > default).
|
|
137
142
|
*
|
|
138
143
|
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
139
144
|
*/
|
|
140
145
|
declare function getMergedContextValues(): Record<string, string>;
|
|
146
|
+
/**
|
|
147
|
+
* Returns the file path used for a context name or path when saving.
|
|
148
|
+
* - If contextName is path-like (absolute or contains path separator), returns that path with .context.json appended if not already present.
|
|
149
|
+
* - Otherwise, if that context was already discovered under cwd, returns its path; else saves under project root (config.root or cwd).
|
|
150
|
+
*
|
|
151
|
+
* @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
|
|
152
|
+
* @returns Absolute path to the context JSON file.
|
|
153
|
+
*/
|
|
154
|
+
declare function getContextWritePath(contextName: string): string;
|
|
141
155
|
|
|
142
156
|
/**
|
|
143
157
|
* Return type for a variable's optional `validator` function. Use a boolean for simple
|
|
@@ -205,8 +219,8 @@ interface ScenvVariable<T> {
|
|
|
205
219
|
error?: unknown;
|
|
206
220
|
}>;
|
|
207
221
|
/**
|
|
208
|
-
* Write the value to
|
|
209
|
-
* If you don't pass a value, the last resolved value is used. Does not prompt
|
|
222
|
+
* Write the value to the save target: if config.saveContextTo is set, to that context file; otherwise to in-memory only.
|
|
223
|
+
* 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
224
|
*/
|
|
211
225
|
save(value?: T): Promise<void>;
|
|
212
226
|
}
|
|
@@ -236,9 +250,9 @@ interface ScenvVariable<T> {
|
|
|
236
250
|
* ## save()
|
|
237
251
|
*
|
|
238
252
|
* The variable has a save(value?) method. It writes the value (or the last resolved value if you omit it)
|
|
239
|
-
* to
|
|
240
|
-
*
|
|
241
|
-
*
|
|
253
|
+
* to the save target (config.saveContextTo file if set, otherwise in-memory). save() does not ask; it saves.
|
|
254
|
+
* When the user is prompted during get(), the value is always saved (to saveContextTo file if set, and always to in-memory)
|
|
255
|
+
* so a second get() on the same variable does not prompt again.
|
|
242
256
|
*
|
|
243
257
|
* @typeParam T - Value type (default string). Use a validator to coerce to number, boolean, etc.
|
|
244
258
|
* @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").
|
|
@@ -256,15 +270,15 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
256
270
|
* Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
|
|
257
271
|
*
|
|
258
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.
|
|
259
274
|
* - `--context a,b,c` – Set context list (replace).
|
|
260
275
|
* - `--add-context x,y` – Add context names.
|
|
261
276
|
* - `--prompt always|never|fallback|no-env` – Prompt mode.
|
|
262
277
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
263
278
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
264
279
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
265
|
-
* - `--save-
|
|
266
|
-
* - `--save-
|
|
267
|
-
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
280
|
+
* - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
|
|
281
|
+
* - `--save-mode all|prompts-only` – When to write to saveContextTo during get(); default is all.
|
|
268
282
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
269
283
|
*
|
|
270
284
|
* @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
|
|
@@ -272,4 +286,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
272
286
|
*/
|
|
273
287
|
declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
|
|
274
288
|
|
|
275
|
-
export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type
|
|
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
|
@@ -4,29 +4,16 @@ 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}: `;
|
|
23
10
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
24
|
-
return new Promise((
|
|
11
|
+
return new Promise((resolve2, reject) => {
|
|
25
12
|
rl.question(message, (answer) => {
|
|
26
13
|
rl.close();
|
|
27
14
|
const trimmed = answer.trim();
|
|
28
15
|
const value = trimmed !== "" ? trimmed : defaultStr;
|
|
29
|
-
|
|
16
|
+
resolve2(value);
|
|
30
17
|
});
|
|
31
18
|
rl.on("error", (err) => {
|
|
32
19
|
rl.close();
|
|
@@ -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,18 +31,15 @@ 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_SAVE_MODE: "saveMode",
|
|
61
36
|
SCENV_LOG_LEVEL: "logLevel"
|
|
62
37
|
};
|
|
63
38
|
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,16 +62,16 @@ 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;
|
|
100
|
-
} else if (configKey === "
|
|
101
|
-
|
|
71
|
+
} else if (configKey === "saveMode") {
|
|
72
|
+
const v = val.toLowerCase();
|
|
73
|
+
if (v === "all" || v === "prompts-only")
|
|
74
|
+
out[configKey] = v;
|
|
102
75
|
} else if (configKey === "logLevel") {
|
|
103
76
|
const v = val.toLowerCase();
|
|
104
77
|
if (LOG_LEVELS.includes(v)) out.logLevel = v;
|
|
@@ -136,12 +109,10 @@ function loadConfigFile(configDir) {
|
|
|
136
109
|
out.ignoreContext = parsed.ignoreContext;
|
|
137
110
|
if (parsed.set && typeof parsed.set === "object" && !Array.isArray(parsed.set))
|
|
138
111
|
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
112
|
if (typeof parsed.saveContextTo === "string")
|
|
142
113
|
out.saveContextTo = parsed.saveContextTo;
|
|
143
|
-
if (typeof parsed.
|
|
144
|
-
|
|
114
|
+
if (typeof parsed.saveMode === "string" && ["all", "prompts-only"].includes(parsed.saveMode))
|
|
115
|
+
out.saveMode = parsed.saveMode;
|
|
145
116
|
if (typeof parsed.root === "string") out.root = parsed.root;
|
|
146
117
|
if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
|
|
147
118
|
out.logLevel = parsed.logLevel;
|
|
@@ -228,9 +199,8 @@ var CONFIG_LOG_KEYS = [
|
|
|
228
199
|
"ignoreEnv",
|
|
229
200
|
"ignoreContext",
|
|
230
201
|
"set",
|
|
231
|
-
"shouldSavePrompt",
|
|
232
202
|
"saveContextTo",
|
|
233
|
-
"
|
|
203
|
+
"saveMode",
|
|
234
204
|
"logLevel"
|
|
235
205
|
];
|
|
236
206
|
function configForLog(config) {
|
|
@@ -251,8 +221,7 @@ function logConfigLoaded(config) {
|
|
|
251
221
|
if (config.ignoreEnv === true) parts.push("ignoreEnv=true");
|
|
252
222
|
if (config.ignoreContext === true) parts.push("ignoreContext=true");
|
|
253
223
|
if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
|
|
254
|
-
if (config.
|
|
255
|
-
if (config.shouldSavePrompt !== void 0) parts.push("shouldSavePrompt=" + config.shouldSavePrompt);
|
|
224
|
+
if (config.saveMode !== void 0) parts.push("saveMode=" + config.saveMode);
|
|
256
225
|
log("info", "config loaded", parts.join(" "));
|
|
257
226
|
}
|
|
258
227
|
if (levelNum >= LEVEL_NUM.debug) {
|
|
@@ -277,10 +246,21 @@ import {
|
|
|
277
246
|
writeFileSync,
|
|
278
247
|
mkdirSync,
|
|
279
248
|
readdirSync,
|
|
280
|
-
statSync
|
|
249
|
+
statSync,
|
|
250
|
+
existsSync as existsSync2
|
|
281
251
|
} from "fs";
|
|
282
|
-
import { join as join2, dirname as dirname2, isAbsolute } from "path";
|
|
252
|
+
import { join as join2, dirname as dirname2, isAbsolute, sep } from "path";
|
|
283
253
|
var CONTEXT_SUFFIX = ".context.json";
|
|
254
|
+
var inMemoryContext = {};
|
|
255
|
+
function getInMemoryContext() {
|
|
256
|
+
return inMemoryContext;
|
|
257
|
+
}
|
|
258
|
+
function setInMemoryContext(key, value) {
|
|
259
|
+
inMemoryContext[key] = value;
|
|
260
|
+
}
|
|
261
|
+
function resetInMemoryContext() {
|
|
262
|
+
inMemoryContext = {};
|
|
263
|
+
}
|
|
284
264
|
function discoverContextPathsInternal(dir, found) {
|
|
285
265
|
let entries;
|
|
286
266
|
try {
|
|
@@ -317,10 +297,23 @@ function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
|
|
|
317
297
|
return found;
|
|
318
298
|
}
|
|
319
299
|
function getContext(contextName, root) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
}
|
|
324
317
|
if (!filePath) {
|
|
325
318
|
log("trace", `getContext: context "${contextName}" not found`);
|
|
326
319
|
return {};
|
|
@@ -345,8 +338,8 @@ function getMergedContextValues() {
|
|
|
345
338
|
const config = loadConfig();
|
|
346
339
|
logConfigLoaded(config);
|
|
347
340
|
if (config.ignoreContext) return {};
|
|
348
|
-
const
|
|
349
|
-
const paths = discoverContextPaths(
|
|
341
|
+
const searchRoot = process.cwd();
|
|
342
|
+
const paths = discoverContextPaths(searchRoot);
|
|
350
343
|
const out = {};
|
|
351
344
|
for (const contextName of config.context ?? []) {
|
|
352
345
|
const filePath = paths.get(contextName);
|
|
@@ -375,13 +368,15 @@ function getMergedContextValues() {
|
|
|
375
368
|
return out;
|
|
376
369
|
}
|
|
377
370
|
function getContextWritePath(contextName) {
|
|
371
|
+
if (isAbsolute(contextName) || contextName.includes(sep)) {
|
|
372
|
+
return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
|
|
373
|
+
}
|
|
378
374
|
const config = loadConfig();
|
|
379
|
-
const
|
|
380
|
-
const paths = discoverContextPaths(root);
|
|
375
|
+
const paths = discoverContextPaths(process.cwd());
|
|
381
376
|
const existing = paths.get(contextName);
|
|
382
377
|
if (existing) return existing;
|
|
383
|
-
const
|
|
384
|
-
return join2(
|
|
378
|
+
const projectRoot = config.root ?? process.cwd();
|
|
379
|
+
return join2(projectRoot, `${contextName}${CONTEXT_SUFFIX}`);
|
|
385
380
|
}
|
|
386
381
|
function writeToContext(contextName, key, value) {
|
|
387
382
|
const path = getContextWritePath(contextName);
|
|
@@ -479,6 +474,12 @@ function scenv(name, options = {}) {
|
|
|
479
474
|
return { raw, source: "env" };
|
|
480
475
|
}
|
|
481
476
|
}
|
|
477
|
+
const mem = getInMemoryContext();
|
|
478
|
+
if (mem[key] !== void 0) {
|
|
479
|
+
log("trace", `resolveRaw: in-memory hit key=${key}`);
|
|
480
|
+
const raw = resolveContextReference(mem[key], key);
|
|
481
|
+
return { raw, source: "context" };
|
|
482
|
+
}
|
|
482
483
|
if (!config.ignoreContext) {
|
|
483
484
|
log("trace", "resolveRaw: checking context");
|
|
484
485
|
const ctx = getMergedContextValues();
|
|
@@ -562,38 +563,14 @@ function scenv(name, options = {}) {
|
|
|
562
563
|
throw new Error(errMsg);
|
|
563
564
|
}
|
|
564
565
|
const final = validated.data;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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";
|
|
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) {
|
|
571
|
+
writeToContext(config.saveContextTo, key, String(final));
|
|
572
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
594
573
|
}
|
|
595
|
-
writeToContext(ctxToSave, key, String(final));
|
|
596
|
-
log("info", `Saved key=${key} to context ${ctxToSave}`);
|
|
597
574
|
}
|
|
598
575
|
return final;
|
|
599
576
|
}
|
|
@@ -614,33 +591,25 @@ function scenv(name, options = {}) {
|
|
|
614
591
|
throw new Error(errMsg);
|
|
615
592
|
}
|
|
616
593
|
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
|
-
);
|
|
594
|
+
setInMemoryContext(key, String(validated.data));
|
|
595
|
+
if (config.saveContextTo) {
|
|
596
|
+
writeToContext(config.saveContextTo, key, String(validated.data));
|
|
597
|
+
log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
|
|
629
598
|
}
|
|
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
599
|
}
|
|
634
600
|
return { get, safeGet, save };
|
|
635
601
|
}
|
|
636
602
|
|
|
637
603
|
// src/cli-args.ts
|
|
604
|
+
import { resolve } from "path";
|
|
638
605
|
function parseScenvArgs(argv) {
|
|
639
606
|
const config = {};
|
|
640
607
|
let i = 0;
|
|
641
608
|
while (i < argv.length) {
|
|
642
609
|
const arg = argv[i];
|
|
643
|
-
if (arg === "--
|
|
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) {
|
|
644
613
|
config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
645
614
|
} else if (arg === "--add-context" && argv[i + 1] !== void 0) {
|
|
646
615
|
config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -660,15 +629,13 @@ function parseScenvArgs(argv) {
|
|
|
660
629
|
config.set = config.set ?? {};
|
|
661
630
|
config.set[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
662
631
|
}
|
|
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
632
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
669
633
|
config.saveContextTo = argv[++i];
|
|
670
|
-
} else if (arg === "--
|
|
671
|
-
|
|
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
|
+
}
|
|
672
639
|
} else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
|
|
673
640
|
const v = argv[++i].toLowerCase();
|
|
674
641
|
if (LOG_LEVELS.includes(v)) {
|
|
@@ -697,10 +664,14 @@ export {
|
|
|
697
664
|
discoverContextPaths,
|
|
698
665
|
getCallbacks,
|
|
699
666
|
getContext,
|
|
667
|
+
getContextWritePath,
|
|
668
|
+
getInMemoryContext,
|
|
700
669
|
getMergedContextValues,
|
|
701
670
|
loadConfig,
|
|
702
671
|
parseScenvArgs,
|
|
703
672
|
resetConfig,
|
|
673
|
+
resetInMemoryContext,
|
|
704
674
|
resetLogState,
|
|
705
|
-
scenv
|
|
675
|
+
scenv,
|
|
676
|
+
setInMemoryContext
|
|
706
677
|
};
|