scenv 0.5.1 → 0.6.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 +73 -34
- package/dist/index.d.cts +13 -10
- package/dist/index.d.ts +13 -10
- package/dist/index.js +74 -35
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -86,13 +86,14 @@ async function defaultAskContext(name, contextNames) {
|
|
|
86
86
|
var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
|
|
87
87
|
var CONFIG_FILENAME = "scenv.config.json";
|
|
88
88
|
var envKeyMap = {
|
|
89
|
-
SCENV_CONTEXT: "
|
|
90
|
-
|
|
89
|
+
SCENV_CONTEXT: "context",
|
|
90
|
+
SCENV_ADD_CONTEXT: "addContext",
|
|
91
91
|
SCENV_PROMPT: "prompt",
|
|
92
92
|
SCENV_IGNORE_ENV: "ignoreEnv",
|
|
93
93
|
SCENV_IGNORE_CONTEXT: "ignoreContext",
|
|
94
94
|
SCENV_SAVE_PROMPT: "shouldSavePrompt",
|
|
95
95
|
SCENV_SAVE_CONTEXT_TO: "saveContextTo",
|
|
96
|
+
SCENV_CONTEXT_DIR: "contextDir",
|
|
96
97
|
SCENV_LOG_LEVEL: "logLevel"
|
|
97
98
|
};
|
|
98
99
|
var programmaticConfig = {};
|
|
@@ -120,7 +121,7 @@ function configFromEnv() {
|
|
|
120
121
|
for (const [envKey, configKey] of Object.entries(envKeyMap)) {
|
|
121
122
|
const val = process.env[envKey];
|
|
122
123
|
if (val === void 0 || val === "") continue;
|
|
123
|
-
if (configKey === "
|
|
124
|
+
if (configKey === "context" || configKey === "addContext") {
|
|
124
125
|
out[configKey] = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
125
126
|
} else if (configKey === "ignoreEnv" || configKey === "ignoreContext") {
|
|
126
127
|
out[configKey] = val === "1" || val === "true" || val.toLowerCase() === "yes";
|
|
@@ -132,6 +133,8 @@ function configFromEnv() {
|
|
|
132
133
|
out[configKey] = v;
|
|
133
134
|
} else if (configKey === "saveContextTo") {
|
|
134
135
|
out.saveContextTo = val;
|
|
136
|
+
} else if (configKey === "contextDir") {
|
|
137
|
+
out.contextDir = val;
|
|
135
138
|
} else if (configKey === "logLevel") {
|
|
136
139
|
const v = val.toLowerCase();
|
|
137
140
|
if (LOG_LEVELS.includes(v)) out.logLevel = v;
|
|
@@ -146,12 +149,20 @@ function loadConfigFile(configDir) {
|
|
|
146
149
|
const raw = (0, import_node_fs.readFileSync)(path, "utf-8");
|
|
147
150
|
const parsed = JSON.parse(raw);
|
|
148
151
|
const out = {};
|
|
149
|
-
if (Array.isArray(parsed.
|
|
150
|
-
out.
|
|
152
|
+
if (Array.isArray(parsed.context))
|
|
153
|
+
out.context = parsed.context.filter(
|
|
151
154
|
(x) => typeof x === "string"
|
|
152
155
|
);
|
|
153
|
-
if (Array.isArray(parsed.
|
|
154
|
-
out.
|
|
156
|
+
else if (Array.isArray(parsed.contexts))
|
|
157
|
+
out.context = parsed.contexts.filter(
|
|
158
|
+
(x) => typeof x === "string"
|
|
159
|
+
);
|
|
160
|
+
if (Array.isArray(parsed.addContext))
|
|
161
|
+
out.addContext = parsed.addContext.filter(
|
|
162
|
+
(x) => typeof x === "string"
|
|
163
|
+
);
|
|
164
|
+
else if (Array.isArray(parsed.addContexts))
|
|
165
|
+
out.addContext = parsed.addContexts.filter(
|
|
155
166
|
(x) => typeof x === "string"
|
|
156
167
|
);
|
|
157
168
|
if (typeof parsed.prompt === "string" && ["always", "never", "fallback", "no-env"].includes(parsed.prompt))
|
|
@@ -165,6 +176,8 @@ function loadConfigFile(configDir) {
|
|
|
165
176
|
out.shouldSavePrompt = parsed.shouldSavePrompt ?? parsed.savePrompt;
|
|
166
177
|
if (typeof parsed.saveContextTo === "string")
|
|
167
178
|
out.saveContextTo = parsed.saveContextTo;
|
|
179
|
+
if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
|
|
180
|
+
else if (typeof parsed.contextsDir === "string") out.contextDir = parsed.contextsDir;
|
|
168
181
|
if (typeof parsed.root === "string") out.root = parsed.root;
|
|
169
182
|
if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
|
|
170
183
|
out.logLevel = parsed.logLevel;
|
|
@@ -180,11 +193,11 @@ function loadConfigFile(configDir) {
|
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
function mergeContexts(fileConfig, envConfig, progConfig) {
|
|
183
|
-
const fromFile = fileConfig.
|
|
184
|
-
const fromEnvAdd = envConfig.
|
|
185
|
-
const fromEnvReplace = envConfig.
|
|
186
|
-
const fromProgAdd = progConfig.
|
|
187
|
-
const fromProgReplace = progConfig.
|
|
196
|
+
const fromFile = fileConfig.context ?? fileConfig.addContext ?? [];
|
|
197
|
+
const fromEnvAdd = envConfig.addContext ?? [];
|
|
198
|
+
const fromEnvReplace = envConfig.context;
|
|
199
|
+
const fromProgAdd = progConfig.addContext ?? [];
|
|
200
|
+
const fromProgReplace = progConfig.context;
|
|
188
201
|
const replace = fromProgReplace ?? fromEnvReplace;
|
|
189
202
|
if (replace !== void 0) return replace;
|
|
190
203
|
const base = [...fromFile];
|
|
@@ -208,8 +221,8 @@ function loadConfig(root) {
|
|
|
208
221
|
...envConfig,
|
|
209
222
|
...programmaticConfig
|
|
210
223
|
};
|
|
211
|
-
merged.
|
|
212
|
-
delete merged.
|
|
224
|
+
merged.context = mergeContexts(fileConfig, envConfig, programmaticConfig);
|
|
225
|
+
delete merged.addContext;
|
|
213
226
|
if (configDir && !merged.root) merged.root = configDir;
|
|
214
227
|
else if (!merged.root) merged.root = startDir;
|
|
215
228
|
return merged;
|
|
@@ -252,7 +265,7 @@ function logConfigLoaded(config) {
|
|
|
252
265
|
"info",
|
|
253
266
|
"config loaded",
|
|
254
267
|
"root=" + (config.root ?? "(cwd)"),
|
|
255
|
-
"
|
|
268
|
+
"context=" + JSON.stringify(config.context ?? [])
|
|
256
269
|
);
|
|
257
270
|
}
|
|
258
271
|
function log(level, msg, ...args) {
|
|
@@ -338,7 +351,7 @@ function getMergedContextValues() {
|
|
|
338
351
|
const root = config.root ?? process.cwd();
|
|
339
352
|
const paths = discoverContextPaths(root);
|
|
340
353
|
const out = {};
|
|
341
|
-
for (const contextName of config.
|
|
354
|
+
for (const contextName of config.context ?? []) {
|
|
342
355
|
const filePath = paths.get(contextName);
|
|
343
356
|
if (!filePath) {
|
|
344
357
|
log("warn", `context "${contextName}" not found (no *.context.json)`);
|
|
@@ -370,7 +383,8 @@ function getContextWritePath(contextName) {
|
|
|
370
383
|
const paths = discoverContextPaths(root);
|
|
371
384
|
const existing = paths.get(contextName);
|
|
372
385
|
if (existing) return existing;
|
|
373
|
-
|
|
386
|
+
const saveDir = config.contextDir ? (0, import_node_path2.isAbsolute)(config.contextDir) ? config.contextDir : (0, import_node_path2.join)(root, config.contextDir) : root;
|
|
387
|
+
return (0, import_node_path2.join)(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
|
|
374
388
|
}
|
|
375
389
|
function writeToContext(contextName, key, value) {
|
|
376
390
|
const path = getContextWritePath(contextName);
|
|
@@ -388,26 +402,49 @@ function writeToContext(contextName, key, value) {
|
|
|
388
402
|
}
|
|
389
403
|
|
|
390
404
|
// src/variable.ts
|
|
391
|
-
var
|
|
405
|
+
var CONTEXT_REF_FULL_REGEX = /^@([^:]+):(.+)$/;
|
|
406
|
+
var CONTEXT_REF_SHORT_REGEX = /^@([^:]+)$/;
|
|
407
|
+
var ENV_REF_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*|\{[A-Za-z_][A-Za-z0-9_]*\})/g;
|
|
392
408
|
var MAX_CONTEXT_REF_DEPTH = 10;
|
|
393
|
-
function
|
|
409
|
+
function expandEnvReferences(str) {
|
|
410
|
+
return str.replace(ENV_REF_REGEX, (_, name) => {
|
|
411
|
+
const key = name.startsWith("{") ? name.slice(1, -1) : name;
|
|
412
|
+
return process.env[key] ?? "";
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function resolveContextReference(raw, currentKey, depth = 0) {
|
|
394
416
|
if (depth >= MAX_CONTEXT_REF_DEPTH) {
|
|
395
417
|
throw new Error(
|
|
396
418
|
`Context reference resolution exceeded max depth (${MAX_CONTEXT_REF_DEPTH}): possible circular reference`
|
|
397
419
|
);
|
|
398
420
|
}
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
421
|
+
const afterEnv = expandEnvReferences(raw);
|
|
422
|
+
let contextName;
|
|
423
|
+
let refKey;
|
|
424
|
+
const fullMatch = afterEnv.match(CONTEXT_REF_FULL_REGEX);
|
|
425
|
+
if (fullMatch) {
|
|
426
|
+
[, contextName, refKey] = fullMatch;
|
|
427
|
+
} else {
|
|
428
|
+
const shortMatch = afterEnv.match(CONTEXT_REF_SHORT_REGEX);
|
|
429
|
+
if (!shortMatch) return afterEnv;
|
|
430
|
+
contextName = shortMatch[1];
|
|
431
|
+
if (currentKey === void 0) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
`Context reference @${contextName} (short form) cannot be resolved without a variable key. Use @${contextName}:<key> to specify a key.`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
refKey = currentKey;
|
|
437
|
+
}
|
|
402
438
|
const ctx = getContext(contextName);
|
|
403
439
|
const resolved = ctx[refKey];
|
|
404
440
|
if (resolved === void 0) {
|
|
405
441
|
const hasContext = Object.keys(ctx).length > 0;
|
|
406
|
-
const
|
|
442
|
+
const displayRef = refKey === currentKey ? `@${contextName}` : `@${contextName}:${refKey}`;
|
|
443
|
+
const msg = hasContext ? `Context reference ${displayRef} could not be resolved: key "${refKey}" is not defined in context "${contextName}".` : `Context reference ${displayRef} could not be resolved: context "${contextName}" not found (no ${contextName}.context.json).`;
|
|
407
444
|
log("error", msg);
|
|
408
445
|
throw new Error(msg);
|
|
409
446
|
}
|
|
410
|
-
return resolveContextReference(resolved, depth + 1);
|
|
447
|
+
return resolveContextReference(resolved, currentKey, depth + 1);
|
|
411
448
|
}
|
|
412
449
|
function defaultKeyFromName(name) {
|
|
413
450
|
return name.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/gi, "");
|
|
@@ -433,7 +470,7 @@ function scenv(name, options = {}) {
|
|
|
433
470
|
log("trace", `resolveRaw: checking set for key=${key}`);
|
|
434
471
|
if (config.set?.[key] !== void 0) {
|
|
435
472
|
log("trace", `resolveRaw: set hit key=${key}`);
|
|
436
|
-
const raw = resolveContextReference(config.set[key]);
|
|
473
|
+
const raw = resolveContextReference(config.set[key], key);
|
|
437
474
|
return { raw, source: "set" };
|
|
438
475
|
}
|
|
439
476
|
if (!config.ignoreEnv) {
|
|
@@ -441,7 +478,7 @@ function scenv(name, options = {}) {
|
|
|
441
478
|
const envVal = process.env[envKey];
|
|
442
479
|
if (envVal !== void 0 && envVal !== "") {
|
|
443
480
|
log("trace", "resolveRaw: env hit");
|
|
444
|
-
const raw = resolveContextReference(envVal);
|
|
481
|
+
const raw = resolveContextReference(envVal, key);
|
|
445
482
|
return { raw, source: "env" };
|
|
446
483
|
}
|
|
447
484
|
}
|
|
@@ -450,7 +487,7 @@ function scenv(name, options = {}) {
|
|
|
450
487
|
const ctx = getMergedContextValues();
|
|
451
488
|
if (ctx[key] !== void 0) {
|
|
452
489
|
log("trace", `resolveRaw: context hit key=${key}`);
|
|
453
|
-
const raw = resolveContextReference(ctx[key]);
|
|
490
|
+
const raw = resolveContextReference(ctx[key], key);
|
|
454
491
|
return { raw, source: "context" };
|
|
455
492
|
}
|
|
456
493
|
}
|
|
@@ -477,7 +514,7 @@ function scenv(name, options = {}) {
|
|
|
477
514
|
`prompt decision key=${key} prompt=${config.prompt ?? "fallback"} hadValue=${hadValue} hadEnv=${hadEnv} -> ${doPrompt ? "prompt" : "no prompt"}`
|
|
478
515
|
);
|
|
479
516
|
const effectiveDefault = overrides?.default !== void 0 ? overrides.default : defaultValue;
|
|
480
|
-
const resolvedDefault = effectiveDefault === void 0 ? void 0 : typeof effectiveDefault === "string" ? resolveContextReference(effectiveDefault) : effectiveDefault;
|
|
517
|
+
const resolvedDefault = effectiveDefault === void 0 ? void 0 : typeof effectiveDefault === "string" ? resolveContextReference(effectiveDefault, key) : effectiveDefault;
|
|
481
518
|
let wasPrompted = false;
|
|
482
519
|
let value;
|
|
483
520
|
let resolvedFrom;
|
|
@@ -491,7 +528,7 @@ function scenv(name, options = {}) {
|
|
|
491
528
|
}
|
|
492
529
|
const defaultForPrompt = raw !== void 0 ? raw : resolvedDefault;
|
|
493
530
|
let promptedValue = await Promise.resolve(fn(name, defaultForPrompt));
|
|
494
|
-
value = typeof promptedValue === "string" ? resolveContextReference(promptedValue) : promptedValue;
|
|
531
|
+
value = typeof promptedValue === "string" ? resolveContextReference(promptedValue, key) : promptedValue;
|
|
495
532
|
wasPrompted = true;
|
|
496
533
|
resolvedFrom = "prompt";
|
|
497
534
|
} else if (raw !== void 0) {
|
|
@@ -546,7 +583,7 @@ function scenv(name, options = {}) {
|
|
|
546
583
|
}
|
|
547
584
|
if (!doSave) return final;
|
|
548
585
|
const callbacks = getCallbacks();
|
|
549
|
-
const contextNames = config.
|
|
586
|
+
const contextNames = config.context ?? [];
|
|
550
587
|
let ctxToSave;
|
|
551
588
|
if (config.saveContextTo === "ask") {
|
|
552
589
|
if (typeof callbacks.onAskContext !== "function") {
|
|
@@ -590,10 +627,10 @@ function scenv(name, options = {}) {
|
|
|
590
627
|
}
|
|
591
628
|
contextName = await callbacks.onAskContext(
|
|
592
629
|
name,
|
|
593
|
-
config.
|
|
630
|
+
config.context ?? []
|
|
594
631
|
);
|
|
595
632
|
}
|
|
596
|
-
if (!contextName) contextName = config.
|
|
633
|
+
if (!contextName) contextName = config.context?.[0] ?? "default";
|
|
597
634
|
writeToContext(contextName, key, String(validated.data));
|
|
598
635
|
log("info", `Saved key=${key} to context ${contextName}`);
|
|
599
636
|
}
|
|
@@ -607,9 +644,9 @@ function parseScenvArgs(argv) {
|
|
|
607
644
|
while (i < argv.length) {
|
|
608
645
|
const arg = argv[i];
|
|
609
646
|
if (arg === "--context" && argv[i + 1] !== void 0) {
|
|
610
|
-
config.
|
|
647
|
+
config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
611
648
|
} else if (arg === "--add-context" && argv[i + 1] !== void 0) {
|
|
612
|
-
config.
|
|
649
|
+
config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
613
650
|
} else if (arg === "--prompt" && argv[i + 1] !== void 0) {
|
|
614
651
|
const v = argv[++i].toLowerCase();
|
|
615
652
|
if (["always", "never", "fallback", "no-env"].includes(v)) {
|
|
@@ -633,6 +670,8 @@ function parseScenvArgs(argv) {
|
|
|
633
670
|
}
|
|
634
671
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
635
672
|
config.saveContextTo = argv[++i];
|
|
673
|
+
} else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
|
|
674
|
+
config.contextDir = argv[++i];
|
|
636
675
|
} else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
|
|
637
676
|
const v = argv[++i].toLowerCase();
|
|
638
677
|
if (LOG_LEVELS.includes(v)) {
|
package/dist/index.d.cts
CHANGED
|
@@ -27,9 +27,9 @@ type LogLevel = (typeof LOG_LEVELS)[number];
|
|
|
27
27
|
*/
|
|
28
28
|
interface ScenvConfig {
|
|
29
29
|
/** Replace the context list entirely (CLI: `--context a,b,c`). Loaded in order; later overwrites earlier for same key. */
|
|
30
|
-
|
|
31
|
-
/** Merge these context names with existing (CLI: `--add-context a,b,c`). Ignored if `
|
|
32
|
-
|
|
30
|
+
context?: string[];
|
|
31
|
+
/** Merge these context names with existing (CLI: `--add-context a,b,c`). Ignored if `context` is set in the same layer. */
|
|
32
|
+
addContext?: string[];
|
|
33
33
|
/** When to prompt for a variable value. See {@link PromptMode}. Default is `"fallback"`. */
|
|
34
34
|
prompt?: PromptMode;
|
|
35
35
|
/** If true, environment variables are not used during resolution. */
|
|
@@ -42,6 +42,8 @@ interface ScenvConfig {
|
|
|
42
42
|
shouldSavePrompt?: SavePromptMode;
|
|
43
43
|
/** Target context for saving: a context name, or `"ask"` to use {@link ScenvCallbacks.onAskContext}. */
|
|
44
44
|
saveContextTo?: "ask" | string;
|
|
45
|
+
/** 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
|
+
contextDir?: string;
|
|
45
47
|
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
46
48
|
root?: string;
|
|
47
49
|
/** Logging level. Default is `"none"`. Messages go to stderr. */
|
|
@@ -79,7 +81,7 @@ declare function getCallbacks(): ScenvCallbacks;
|
|
|
79
81
|
* Use this to read the effective config (e.g. for logging or conditional logic).
|
|
80
82
|
*
|
|
81
83
|
* @param root - Optional directory to start searching for scenv.config.json. If omitted, uses programmatic config.root or process.cwd().
|
|
82
|
-
* @returns The merged {@link ScenvConfig} with at least `root` and `
|
|
84
|
+
* @returns The merged {@link ScenvConfig} with at least `root` and `context` defined.
|
|
83
85
|
*/
|
|
84
86
|
declare function loadConfig(root?: string): ScenvConfig;
|
|
85
87
|
/**
|
|
@@ -119,7 +121,7 @@ declare function resetLogState(): void;
|
|
|
119
121
|
declare function discoverContextPaths(dir: string, found?: Map<string, string>): Map<string, string>;
|
|
120
122
|
/**
|
|
121
123
|
* Loads key-value pairs from a single context file. Used when resolving @context:key references.
|
|
122
|
-
* Does not depend on config.
|
|
124
|
+
* Does not depend on config.context or ignoreContext; the context file is read if it exists
|
|
123
125
|
* under the config root.
|
|
124
126
|
*
|
|
125
127
|
* @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
|
|
@@ -128,12 +130,12 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
128
130
|
*/
|
|
129
131
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
130
132
|
/**
|
|
131
|
-
* Loads and merges context values from the current config. Respects {@link ScenvConfig.
|
|
133
|
+
* Loads and merges context values from the current config. Respects {@link ScenvConfig.context}
|
|
132
134
|
* order and {@link ScenvConfig.ignoreContext}. Each context file is a JSON object of string
|
|
133
135
|
* key-value pairs; later contexts overwrite earlier for the same key. Used during variable
|
|
134
136
|
* resolution (set > env > context > default).
|
|
135
137
|
*
|
|
136
|
-
* @returns A flat record of key → string value. Empty if ignoreContext is true or no
|
|
138
|
+
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
137
139
|
*/
|
|
138
140
|
declare function getMergedContextValues(): Record<string, string>;
|
|
139
141
|
|
|
@@ -218,7 +220,7 @@ interface ScenvVariable<T> {
|
|
|
218
220
|
* get() first looks for a raw value in this order, stopping at the first found:
|
|
219
221
|
* - Set overrides from config (e.g. from --set key=value or configure({ set: { key: "value" } }))
|
|
220
222
|
* - Environment variable (e.g. API_URL for key "api_url")
|
|
221
|
-
* - Context files (merged key-value from the
|
|
223
|
+
* - Context files (merged key-value from the context list in config, e.g. dev.context.json)
|
|
222
224
|
*
|
|
223
225
|
* If config says to prompt (see prompt mode in config: "always", "fallback", "no-env"), the prompt callback
|
|
224
226
|
* may run. When it runs, it receives the variable name and a suggested value (the raw value if any, otherwise
|
|
@@ -254,14 +256,15 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
254
256
|
* Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
|
|
255
257
|
*
|
|
256
258
|
* Supported flags:
|
|
257
|
-
* - `--context a,b,c` – Set
|
|
258
|
-
* - `--add-context x,y` – Add
|
|
259
|
+
* - `--context a,b,c` – Set context list (replace).
|
|
260
|
+
* - `--add-context x,y` – Add context names.
|
|
259
261
|
* - `--prompt always|never|fallback|no-env` – Prompt mode.
|
|
260
262
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
261
263
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
262
264
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
263
265
|
* - `--save-prompt always|never|ask` – shouldSavePrompt.
|
|
264
266
|
* - `--save-context-to name` – saveContextTo.
|
|
267
|
+
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
265
268
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
266
269
|
*
|
|
267
270
|
* @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
|
package/dist/index.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ type LogLevel = (typeof LOG_LEVELS)[number];
|
|
|
27
27
|
*/
|
|
28
28
|
interface ScenvConfig {
|
|
29
29
|
/** Replace the context list entirely (CLI: `--context a,b,c`). Loaded in order; later overwrites earlier for same key. */
|
|
30
|
-
|
|
31
|
-
/** Merge these context names with existing (CLI: `--add-context a,b,c`). Ignored if `
|
|
32
|
-
|
|
30
|
+
context?: string[];
|
|
31
|
+
/** Merge these context names with existing (CLI: `--add-context a,b,c`). Ignored if `context` is set in the same layer. */
|
|
32
|
+
addContext?: string[];
|
|
33
33
|
/** When to prompt for a variable value. See {@link PromptMode}. Default is `"fallback"`. */
|
|
34
34
|
prompt?: PromptMode;
|
|
35
35
|
/** If true, environment variables are not used during resolution. */
|
|
@@ -42,6 +42,8 @@ interface ScenvConfig {
|
|
|
42
42
|
shouldSavePrompt?: SavePromptMode;
|
|
43
43
|
/** Target context for saving: a context name, or `"ask"` to use {@link ScenvCallbacks.onAskContext}. */
|
|
44
44
|
saveContextTo?: "ask" | string;
|
|
45
|
+
/** 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
|
+
contextDir?: string;
|
|
45
47
|
/** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
|
|
46
48
|
root?: string;
|
|
47
49
|
/** Logging level. Default is `"none"`. Messages go to stderr. */
|
|
@@ -79,7 +81,7 @@ declare function getCallbacks(): ScenvCallbacks;
|
|
|
79
81
|
* Use this to read the effective config (e.g. for logging or conditional logic).
|
|
80
82
|
*
|
|
81
83
|
* @param root - Optional directory to start searching for scenv.config.json. If omitted, uses programmatic config.root or process.cwd().
|
|
82
|
-
* @returns The merged {@link ScenvConfig} with at least `root` and `
|
|
84
|
+
* @returns The merged {@link ScenvConfig} with at least `root` and `context` defined.
|
|
83
85
|
*/
|
|
84
86
|
declare function loadConfig(root?: string): ScenvConfig;
|
|
85
87
|
/**
|
|
@@ -119,7 +121,7 @@ declare function resetLogState(): void;
|
|
|
119
121
|
declare function discoverContextPaths(dir: string, found?: Map<string, string>): Map<string, string>;
|
|
120
122
|
/**
|
|
121
123
|
* Loads key-value pairs from a single context file. Used when resolving @context:key references.
|
|
122
|
-
* Does not depend on config.
|
|
124
|
+
* Does not depend on config.context or ignoreContext; the context file is read if it exists
|
|
123
125
|
* under the config root.
|
|
124
126
|
*
|
|
125
127
|
* @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
|
|
@@ -128,12 +130,12 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
|
|
|
128
130
|
*/
|
|
129
131
|
declare function getContext(contextName: string, root?: string): Record<string, string>;
|
|
130
132
|
/**
|
|
131
|
-
* Loads and merges context values from the current config. Respects {@link ScenvConfig.
|
|
133
|
+
* Loads and merges context values from the current config. Respects {@link ScenvConfig.context}
|
|
132
134
|
* order and {@link ScenvConfig.ignoreContext}. Each context file is a JSON object of string
|
|
133
135
|
* key-value pairs; later contexts overwrite earlier for the same key. Used during variable
|
|
134
136
|
* resolution (set > env > context > default).
|
|
135
137
|
*
|
|
136
|
-
* @returns A flat record of key → string value. Empty if ignoreContext is true or no
|
|
138
|
+
* @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
|
|
137
139
|
*/
|
|
138
140
|
declare function getMergedContextValues(): Record<string, string>;
|
|
139
141
|
|
|
@@ -218,7 +220,7 @@ interface ScenvVariable<T> {
|
|
|
218
220
|
* get() first looks for a raw value in this order, stopping at the first found:
|
|
219
221
|
* - Set overrides from config (e.g. from --set key=value or configure({ set: { key: "value" } }))
|
|
220
222
|
* - Environment variable (e.g. API_URL for key "api_url")
|
|
221
|
-
* - Context files (merged key-value from the
|
|
223
|
+
* - Context files (merged key-value from the context list in config, e.g. dev.context.json)
|
|
222
224
|
*
|
|
223
225
|
* If config says to prompt (see prompt mode in config: "always", "fallback", "no-env"), the prompt callback
|
|
224
226
|
* may run. When it runs, it receives the variable name and a suggested value (the raw value if any, otherwise
|
|
@@ -254,14 +256,15 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
|
|
|
254
256
|
* Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
|
|
255
257
|
*
|
|
256
258
|
* Supported flags:
|
|
257
|
-
* - `--context a,b,c` – Set
|
|
258
|
-
* - `--add-context x,y` – Add
|
|
259
|
+
* - `--context a,b,c` – Set context list (replace).
|
|
260
|
+
* - `--add-context x,y` – Add context names.
|
|
259
261
|
* - `--prompt always|never|fallback|no-env` – Prompt mode.
|
|
260
262
|
* - `--ignore-env` – Set ignoreEnv to true.
|
|
261
263
|
* - `--ignore-context` – Set ignoreContext to true.
|
|
262
264
|
* - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
|
|
263
265
|
* - `--save-prompt always|never|ask` – shouldSavePrompt.
|
|
264
266
|
* - `--save-context-to name` – saveContextTo.
|
|
267
|
+
* - `--context-dir path` – contextDir (directory to save context files to by default).
|
|
265
268
|
* - `--log-level level`, `--log level`, `--log=level` – logLevel.
|
|
266
269
|
*
|
|
267
270
|
* @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
|
package/dist/index.js
CHANGED
|
@@ -50,13 +50,14 @@ async function defaultAskContext(name, contextNames) {
|
|
|
50
50
|
var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
|
|
51
51
|
var CONFIG_FILENAME = "scenv.config.json";
|
|
52
52
|
var envKeyMap = {
|
|
53
|
-
SCENV_CONTEXT: "
|
|
54
|
-
|
|
53
|
+
SCENV_CONTEXT: "context",
|
|
54
|
+
SCENV_ADD_CONTEXT: "addContext",
|
|
55
55
|
SCENV_PROMPT: "prompt",
|
|
56
56
|
SCENV_IGNORE_ENV: "ignoreEnv",
|
|
57
57
|
SCENV_IGNORE_CONTEXT: "ignoreContext",
|
|
58
58
|
SCENV_SAVE_PROMPT: "shouldSavePrompt",
|
|
59
59
|
SCENV_SAVE_CONTEXT_TO: "saveContextTo",
|
|
60
|
+
SCENV_CONTEXT_DIR: "contextDir",
|
|
60
61
|
SCENV_LOG_LEVEL: "logLevel"
|
|
61
62
|
};
|
|
62
63
|
var programmaticConfig = {};
|
|
@@ -84,7 +85,7 @@ function configFromEnv() {
|
|
|
84
85
|
for (const [envKey, configKey] of Object.entries(envKeyMap)) {
|
|
85
86
|
const val = process.env[envKey];
|
|
86
87
|
if (val === void 0 || val === "") continue;
|
|
87
|
-
if (configKey === "
|
|
88
|
+
if (configKey === "context" || configKey === "addContext") {
|
|
88
89
|
out[configKey] = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
89
90
|
} else if (configKey === "ignoreEnv" || configKey === "ignoreContext") {
|
|
90
91
|
out[configKey] = val === "1" || val === "true" || val.toLowerCase() === "yes";
|
|
@@ -96,6 +97,8 @@ function configFromEnv() {
|
|
|
96
97
|
out[configKey] = v;
|
|
97
98
|
} else if (configKey === "saveContextTo") {
|
|
98
99
|
out.saveContextTo = val;
|
|
100
|
+
} else if (configKey === "contextDir") {
|
|
101
|
+
out.contextDir = val;
|
|
99
102
|
} else if (configKey === "logLevel") {
|
|
100
103
|
const v = val.toLowerCase();
|
|
101
104
|
if (LOG_LEVELS.includes(v)) out.logLevel = v;
|
|
@@ -110,12 +113,20 @@ function loadConfigFile(configDir) {
|
|
|
110
113
|
const raw = readFileSync(path, "utf-8");
|
|
111
114
|
const parsed = JSON.parse(raw);
|
|
112
115
|
const out = {};
|
|
113
|
-
if (Array.isArray(parsed.
|
|
114
|
-
out.
|
|
116
|
+
if (Array.isArray(parsed.context))
|
|
117
|
+
out.context = parsed.context.filter(
|
|
115
118
|
(x) => typeof x === "string"
|
|
116
119
|
);
|
|
117
|
-
if (Array.isArray(parsed.
|
|
118
|
-
out.
|
|
120
|
+
else if (Array.isArray(parsed.contexts))
|
|
121
|
+
out.context = parsed.contexts.filter(
|
|
122
|
+
(x) => typeof x === "string"
|
|
123
|
+
);
|
|
124
|
+
if (Array.isArray(parsed.addContext))
|
|
125
|
+
out.addContext = parsed.addContext.filter(
|
|
126
|
+
(x) => typeof x === "string"
|
|
127
|
+
);
|
|
128
|
+
else if (Array.isArray(parsed.addContexts))
|
|
129
|
+
out.addContext = parsed.addContexts.filter(
|
|
119
130
|
(x) => typeof x === "string"
|
|
120
131
|
);
|
|
121
132
|
if (typeof parsed.prompt === "string" && ["always", "never", "fallback", "no-env"].includes(parsed.prompt))
|
|
@@ -129,6 +140,8 @@ function loadConfigFile(configDir) {
|
|
|
129
140
|
out.shouldSavePrompt = parsed.shouldSavePrompt ?? parsed.savePrompt;
|
|
130
141
|
if (typeof parsed.saveContextTo === "string")
|
|
131
142
|
out.saveContextTo = parsed.saveContextTo;
|
|
143
|
+
if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
|
|
144
|
+
else if (typeof parsed.contextsDir === "string") out.contextDir = parsed.contextsDir;
|
|
132
145
|
if (typeof parsed.root === "string") out.root = parsed.root;
|
|
133
146
|
if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
|
|
134
147
|
out.logLevel = parsed.logLevel;
|
|
@@ -144,11 +157,11 @@ function loadConfigFile(configDir) {
|
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
function mergeContexts(fileConfig, envConfig, progConfig) {
|
|
147
|
-
const fromFile = fileConfig.
|
|
148
|
-
const fromEnvAdd = envConfig.
|
|
149
|
-
const fromEnvReplace = envConfig.
|
|
150
|
-
const fromProgAdd = progConfig.
|
|
151
|
-
const fromProgReplace = progConfig.
|
|
160
|
+
const fromFile = fileConfig.context ?? fileConfig.addContext ?? [];
|
|
161
|
+
const fromEnvAdd = envConfig.addContext ?? [];
|
|
162
|
+
const fromEnvReplace = envConfig.context;
|
|
163
|
+
const fromProgAdd = progConfig.addContext ?? [];
|
|
164
|
+
const fromProgReplace = progConfig.context;
|
|
152
165
|
const replace = fromProgReplace ?? fromEnvReplace;
|
|
153
166
|
if (replace !== void 0) return replace;
|
|
154
167
|
const base = [...fromFile];
|
|
@@ -172,8 +185,8 @@ function loadConfig(root) {
|
|
|
172
185
|
...envConfig,
|
|
173
186
|
...programmaticConfig
|
|
174
187
|
};
|
|
175
|
-
merged.
|
|
176
|
-
delete merged.
|
|
188
|
+
merged.context = mergeContexts(fileConfig, envConfig, programmaticConfig);
|
|
189
|
+
delete merged.addContext;
|
|
177
190
|
if (configDir && !merged.root) merged.root = configDir;
|
|
178
191
|
else if (!merged.root) merged.root = startDir;
|
|
179
192
|
return merged;
|
|
@@ -216,7 +229,7 @@ function logConfigLoaded(config) {
|
|
|
216
229
|
"info",
|
|
217
230
|
"config loaded",
|
|
218
231
|
"root=" + (config.root ?? "(cwd)"),
|
|
219
|
-
"
|
|
232
|
+
"context=" + JSON.stringify(config.context ?? [])
|
|
220
233
|
);
|
|
221
234
|
}
|
|
222
235
|
function log(level, msg, ...args) {
|
|
@@ -239,7 +252,7 @@ import {
|
|
|
239
252
|
readdirSync,
|
|
240
253
|
statSync
|
|
241
254
|
} from "fs";
|
|
242
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
255
|
+
import { join as join2, dirname as dirname2, isAbsolute } from "path";
|
|
243
256
|
var CONTEXT_SUFFIX = ".context.json";
|
|
244
257
|
function discoverContextPathsInternal(dir, found) {
|
|
245
258
|
let entries;
|
|
@@ -308,7 +321,7 @@ function getMergedContextValues() {
|
|
|
308
321
|
const root = config.root ?? process.cwd();
|
|
309
322
|
const paths = discoverContextPaths(root);
|
|
310
323
|
const out = {};
|
|
311
|
-
for (const contextName of config.
|
|
324
|
+
for (const contextName of config.context ?? []) {
|
|
312
325
|
const filePath = paths.get(contextName);
|
|
313
326
|
if (!filePath) {
|
|
314
327
|
log("warn", `context "${contextName}" not found (no *.context.json)`);
|
|
@@ -340,7 +353,8 @@ function getContextWritePath(contextName) {
|
|
|
340
353
|
const paths = discoverContextPaths(root);
|
|
341
354
|
const existing = paths.get(contextName);
|
|
342
355
|
if (existing) return existing;
|
|
343
|
-
|
|
356
|
+
const saveDir = config.contextDir ? isAbsolute(config.contextDir) ? config.contextDir : join2(root, config.contextDir) : root;
|
|
357
|
+
return join2(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
|
|
344
358
|
}
|
|
345
359
|
function writeToContext(contextName, key, value) {
|
|
346
360
|
const path = getContextWritePath(contextName);
|
|
@@ -358,26 +372,49 @@ function writeToContext(contextName, key, value) {
|
|
|
358
372
|
}
|
|
359
373
|
|
|
360
374
|
// src/variable.ts
|
|
361
|
-
var
|
|
375
|
+
var CONTEXT_REF_FULL_REGEX = /^@([^:]+):(.+)$/;
|
|
376
|
+
var CONTEXT_REF_SHORT_REGEX = /^@([^:]+)$/;
|
|
377
|
+
var ENV_REF_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*|\{[A-Za-z_][A-Za-z0-9_]*\})/g;
|
|
362
378
|
var MAX_CONTEXT_REF_DEPTH = 10;
|
|
363
|
-
function
|
|
379
|
+
function expandEnvReferences(str) {
|
|
380
|
+
return str.replace(ENV_REF_REGEX, (_, name) => {
|
|
381
|
+
const key = name.startsWith("{") ? name.slice(1, -1) : name;
|
|
382
|
+
return process.env[key] ?? "";
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function resolveContextReference(raw, currentKey, depth = 0) {
|
|
364
386
|
if (depth >= MAX_CONTEXT_REF_DEPTH) {
|
|
365
387
|
throw new Error(
|
|
366
388
|
`Context reference resolution exceeded max depth (${MAX_CONTEXT_REF_DEPTH}): possible circular reference`
|
|
367
389
|
);
|
|
368
390
|
}
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
391
|
+
const afterEnv = expandEnvReferences(raw);
|
|
392
|
+
let contextName;
|
|
393
|
+
let refKey;
|
|
394
|
+
const fullMatch = afterEnv.match(CONTEXT_REF_FULL_REGEX);
|
|
395
|
+
if (fullMatch) {
|
|
396
|
+
[, contextName, refKey] = fullMatch;
|
|
397
|
+
} else {
|
|
398
|
+
const shortMatch = afterEnv.match(CONTEXT_REF_SHORT_REGEX);
|
|
399
|
+
if (!shortMatch) return afterEnv;
|
|
400
|
+
contextName = shortMatch[1];
|
|
401
|
+
if (currentKey === void 0) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`Context reference @${contextName} (short form) cannot be resolved without a variable key. Use @${contextName}:<key> to specify a key.`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
refKey = currentKey;
|
|
407
|
+
}
|
|
372
408
|
const ctx = getContext(contextName);
|
|
373
409
|
const resolved = ctx[refKey];
|
|
374
410
|
if (resolved === void 0) {
|
|
375
411
|
const hasContext = Object.keys(ctx).length > 0;
|
|
376
|
-
const
|
|
412
|
+
const displayRef = refKey === currentKey ? `@${contextName}` : `@${contextName}:${refKey}`;
|
|
413
|
+
const msg = hasContext ? `Context reference ${displayRef} could not be resolved: key "${refKey}" is not defined in context "${contextName}".` : `Context reference ${displayRef} could not be resolved: context "${contextName}" not found (no ${contextName}.context.json).`;
|
|
377
414
|
log("error", msg);
|
|
378
415
|
throw new Error(msg);
|
|
379
416
|
}
|
|
380
|
-
return resolveContextReference(resolved, depth + 1);
|
|
417
|
+
return resolveContextReference(resolved, currentKey, depth + 1);
|
|
381
418
|
}
|
|
382
419
|
function defaultKeyFromName(name) {
|
|
383
420
|
return name.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/gi, "");
|
|
@@ -403,7 +440,7 @@ function scenv(name, options = {}) {
|
|
|
403
440
|
log("trace", `resolveRaw: checking set for key=${key}`);
|
|
404
441
|
if (config.set?.[key] !== void 0) {
|
|
405
442
|
log("trace", `resolveRaw: set hit key=${key}`);
|
|
406
|
-
const raw = resolveContextReference(config.set[key]);
|
|
443
|
+
const raw = resolveContextReference(config.set[key], key);
|
|
407
444
|
return { raw, source: "set" };
|
|
408
445
|
}
|
|
409
446
|
if (!config.ignoreEnv) {
|
|
@@ -411,7 +448,7 @@ function scenv(name, options = {}) {
|
|
|
411
448
|
const envVal = process.env[envKey];
|
|
412
449
|
if (envVal !== void 0 && envVal !== "") {
|
|
413
450
|
log("trace", "resolveRaw: env hit");
|
|
414
|
-
const raw = resolveContextReference(envVal);
|
|
451
|
+
const raw = resolveContextReference(envVal, key);
|
|
415
452
|
return { raw, source: "env" };
|
|
416
453
|
}
|
|
417
454
|
}
|
|
@@ -420,7 +457,7 @@ function scenv(name, options = {}) {
|
|
|
420
457
|
const ctx = getMergedContextValues();
|
|
421
458
|
if (ctx[key] !== void 0) {
|
|
422
459
|
log("trace", `resolveRaw: context hit key=${key}`);
|
|
423
|
-
const raw = resolveContextReference(ctx[key]);
|
|
460
|
+
const raw = resolveContextReference(ctx[key], key);
|
|
424
461
|
return { raw, source: "context" };
|
|
425
462
|
}
|
|
426
463
|
}
|
|
@@ -447,7 +484,7 @@ function scenv(name, options = {}) {
|
|
|
447
484
|
`prompt decision key=${key} prompt=${config.prompt ?? "fallback"} hadValue=${hadValue} hadEnv=${hadEnv} -> ${doPrompt ? "prompt" : "no prompt"}`
|
|
448
485
|
);
|
|
449
486
|
const effectiveDefault = overrides?.default !== void 0 ? overrides.default : defaultValue;
|
|
450
|
-
const resolvedDefault = effectiveDefault === void 0 ? void 0 : typeof effectiveDefault === "string" ? resolveContextReference(effectiveDefault) : effectiveDefault;
|
|
487
|
+
const resolvedDefault = effectiveDefault === void 0 ? void 0 : typeof effectiveDefault === "string" ? resolveContextReference(effectiveDefault, key) : effectiveDefault;
|
|
451
488
|
let wasPrompted = false;
|
|
452
489
|
let value;
|
|
453
490
|
let resolvedFrom;
|
|
@@ -461,7 +498,7 @@ function scenv(name, options = {}) {
|
|
|
461
498
|
}
|
|
462
499
|
const defaultForPrompt = raw !== void 0 ? raw : resolvedDefault;
|
|
463
500
|
let promptedValue = await Promise.resolve(fn(name, defaultForPrompt));
|
|
464
|
-
value = typeof promptedValue === "string" ? resolveContextReference(promptedValue) : promptedValue;
|
|
501
|
+
value = typeof promptedValue === "string" ? resolveContextReference(promptedValue, key) : promptedValue;
|
|
465
502
|
wasPrompted = true;
|
|
466
503
|
resolvedFrom = "prompt";
|
|
467
504
|
} else if (raw !== void 0) {
|
|
@@ -516,7 +553,7 @@ function scenv(name, options = {}) {
|
|
|
516
553
|
}
|
|
517
554
|
if (!doSave) return final;
|
|
518
555
|
const callbacks = getCallbacks();
|
|
519
|
-
const contextNames = config.
|
|
556
|
+
const contextNames = config.context ?? [];
|
|
520
557
|
let ctxToSave;
|
|
521
558
|
if (config.saveContextTo === "ask") {
|
|
522
559
|
if (typeof callbacks.onAskContext !== "function") {
|
|
@@ -560,10 +597,10 @@ function scenv(name, options = {}) {
|
|
|
560
597
|
}
|
|
561
598
|
contextName = await callbacks.onAskContext(
|
|
562
599
|
name,
|
|
563
|
-
config.
|
|
600
|
+
config.context ?? []
|
|
564
601
|
);
|
|
565
602
|
}
|
|
566
|
-
if (!contextName) contextName = config.
|
|
603
|
+
if (!contextName) contextName = config.context?.[0] ?? "default";
|
|
567
604
|
writeToContext(contextName, key, String(validated.data));
|
|
568
605
|
log("info", `Saved key=${key} to context ${contextName}`);
|
|
569
606
|
}
|
|
@@ -577,9 +614,9 @@ function parseScenvArgs(argv) {
|
|
|
577
614
|
while (i < argv.length) {
|
|
578
615
|
const arg = argv[i];
|
|
579
616
|
if (arg === "--context" && argv[i + 1] !== void 0) {
|
|
580
|
-
config.
|
|
617
|
+
config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
581
618
|
} else if (arg === "--add-context" && argv[i + 1] !== void 0) {
|
|
582
|
-
config.
|
|
619
|
+
config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
583
620
|
} else if (arg === "--prompt" && argv[i + 1] !== void 0) {
|
|
584
621
|
const v = argv[++i].toLowerCase();
|
|
585
622
|
if (["always", "never", "fallback", "no-env"].includes(v)) {
|
|
@@ -603,6 +640,8 @@ function parseScenvArgs(argv) {
|
|
|
603
640
|
}
|
|
604
641
|
} else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
|
|
605
642
|
config.saveContextTo = argv[++i];
|
|
643
|
+
} else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
|
|
644
|
+
config.contextDir = argv[++i];
|
|
606
645
|
} else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
|
|
607
646
|
const v = argv[++i].toLowerCase();
|
|
608
647
|
if (LOG_LEVELS.includes(v)) {
|