mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 05:42:25 -05:00
feat(cli): support multiple env file argument (#26527)
Closes #26425 ## Overview This PR adds support for specifying multiple environment files as arguments when using the Deno CLI. Subsequent files override pre-existing variables defined in previous files. If the same variable is defined in the environment and in the file, the value from the environment takes precedence. ## Example Usage ```bash deno run --allow-env --env-file --env-file=".env.one" --env-file=".env.two" script.ts ``` --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
73411bb98a
commit
cff6e280c7
18 changed files with 93 additions and 44 deletions
|
@ -613,7 +613,7 @@ pub struct Flags {
|
|||
pub internal: InternalFlags,
|
||||
pub ignore: Vec<String>,
|
||||
pub import_map_path: Option<String>,
|
||||
pub env_file: Option<String>,
|
||||
pub env_file: Option<Vec<String>>,
|
||||
pub inspect_brk: Option<SocketAddr>,
|
||||
pub inspect_wait: Option<SocketAddr>,
|
||||
pub inspect: Option<SocketAddr>,
|
||||
|
@ -3775,12 +3775,14 @@ fn env_file_arg() -> Arg {
|
|||
.help(cstr!(
|
||||
"Load environment variables from local file
|
||||
<p(245)>Only the first environment variable with a given key is used.
|
||||
Existing process environment variables are not overwritten.</>"
|
||||
Existing process environment variables are not overwritten, so if variables with the same names already exist in the environment, their values will be preserved.
|
||||
Where multiple declarations for the same environment variable exist in your .env file, the first one encountered is applied. This is determined by the order of the files you pass as arguments.</>"
|
||||
))
|
||||
.value_hint(ValueHint::FilePath)
|
||||
.default_missing_value(".env")
|
||||
.require_equals(true)
|
||||
.num_args(0..=1)
|
||||
.action(ArgAction::Append)
|
||||
}
|
||||
|
||||
fn reload_arg() -> Arg {
|
||||
|
@ -5487,7 +5489,9 @@ fn import_map_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
|||
}
|
||||
|
||||
fn env_file_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||
flags.env_file = matches.remove_one::<String>("env-file");
|
||||
flags.env_file = matches
|
||||
.get_many::<String>("env-file")
|
||||
.map(|values| values.cloned().collect());
|
||||
}
|
||||
|
||||
fn reload_arg_parse(
|
||||
|
@ -7423,7 +7427,7 @@ mod tests {
|
|||
allow_all: true,
|
||||
..Default::default()
|
||||
},
|
||||
env_file: Some(".example.env".to_owned()),
|
||||
env_file: Some(vec![".example.env".to_owned()]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -7517,7 +7521,7 @@ mod tests {
|
|||
allow_all: true,
|
||||
..Default::default()
|
||||
},
|
||||
env_file: Some(".example.env".to_owned()),
|
||||
env_file: Some(vec![".example.env".to_owned()]),
|
||||
unsafely_ignore_certificate_errors: Some(vec![]),
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -8165,7 +8169,7 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Run(RunFlags::new_default(
|
||||
"script.ts".to_string(),
|
||||
)),
|
||||
env_file: Some(".env".to_owned()),
|
||||
env_file: Some(vec![".env".to_owned()]),
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -8181,7 +8185,7 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Run(RunFlags::new_default(
|
||||
"script.ts".to_string(),
|
||||
)),
|
||||
env_file: Some(".env".to_owned()),
|
||||
env_file: Some(vec![".env".to_owned()]),
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -8214,7 +8218,7 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Run(RunFlags::new_default(
|
||||
"script.ts".to_string(),
|
||||
)),
|
||||
env_file: Some(".another_env".to_owned()),
|
||||
env_file: Some(vec![".another_env".to_owned()]),
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -8235,7 +8239,29 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Run(RunFlags::new_default(
|
||||
"script.ts".to_string(),
|
||||
)),
|
||||
env_file: Some(".another_env".to_owned()),
|
||||
env_file: Some(vec![".another_env".to_owned()]),
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_multiple_env_file_defined() {
|
||||
let r = flags_from_vec(svec![
|
||||
"deno",
|
||||
"run",
|
||||
"--env-file",
|
||||
"--env-file=.two_env",
|
||||
"script.ts"
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
Flags {
|
||||
subcommand: DenoSubcommand::Run(RunFlags::new_default(
|
||||
"script.ts".to_string(),
|
||||
)),
|
||||
env_file: Some(vec![".env".to_owned(), ".two_env".to_owned()]),
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -8378,7 +8404,7 @@ mod tests {
|
|||
allow_read: Some(vec![]),
|
||||
..Default::default()
|
||||
},
|
||||
env_file: Some(".example.env".to_owned()),
|
||||
env_file: Some(vec![".example.env".to_owned()]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -10053,7 +10079,7 @@ mod tests {
|
|||
unsafely_ignore_certificate_errors: Some(vec![]),
|
||||
v8_flags: svec!["--help", "--random-seed=1"],
|
||||
seed: Some(1),
|
||||
env_file: Some(".example.env".to_owned()),
|
||||
env_file: Some(vec![".example.env".to_owned()]),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1128,7 +1128,7 @@ impl CliOptions {
|
|||
self.flags.otel_config()
|
||||
}
|
||||
|
||||
pub fn env_file_name(&self) -> Option<&String> {
|
||||
pub fn env_file_name(&self) -> Option<&Vec<String>> {
|
||||
self.flags.env_file.as_ref()
|
||||
}
|
||||
|
||||
|
@ -1935,19 +1935,22 @@ pub fn config_to_deno_graph_workspace_member(
|
|||
})
|
||||
}
|
||||
|
||||
fn load_env_variables_from_env_file(filename: Option<&String>) {
|
||||
let Some(env_file_name) = filename else {
|
||||
fn load_env_variables_from_env_file(filename: Option<&Vec<String>>) {
|
||||
let Some(env_file_names) = filename else {
|
||||
return;
|
||||
};
|
||||
match from_filename(env_file_name) {
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
match error {
|
||||
|
||||
for env_file_name in env_file_names.iter().rev() {
|
||||
match from_filename(env_file_name) {
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
match error {
|
||||
dotenvy::Error::LineParse(line, index)=> log::info!("{} Parsing failed within the specified environment file: {} at index: {} of the value: {}",colors::yellow("Warning"), env_file_name, index, line),
|
||||
dotenvy::Error::Io(_)=> log::info!("{} The `--env-file` flag was used, but the environment file specified '{}' was not found.",colors::yellow("Warning"),env_file_name),
|
||||
dotenvy::Error::EnvVar(_)=> log::info!("{} One or more of the environment variables isn't present or not unicode within the specified environment file: {}",colors::yellow("Warning"),env_file_name),
|
||||
_ => log::info!("{} Unknown failure occurred with the specified environment file: {}", colors::yellow("Warning"), env_file_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -659,9 +659,15 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
remote_modules_store.add_redirects(&graph.redirects);
|
||||
|
||||
let env_vars_from_env_file = match cli_options.env_file_name() {
|
||||
Some(env_filename) => {
|
||||
log::info!("{} Environment variables from the file \"{}\" were embedded in the generated executable file", crate::colors::yellow("Warning"), env_filename);
|
||||
get_file_env_vars(env_filename.to_string())?
|
||||
Some(env_filenames) => {
|
||||
let mut aggregated_env_vars = IndexMap::new();
|
||||
for env_filename in env_filenames.iter().rev() {
|
||||
log::info!("{} Environment variables from the file \"{}\" were embedded in the generated executable file", crate::colors::yellow("Warning"), env_filename);
|
||||
|
||||
let env_vars = get_file_env_vars(env_filename.to_string())?;
|
||||
aggregated_env_vars.extend(env_vars);
|
||||
}
|
||||
aggregated_env_vars
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
|
|
@ -418,16 +418,6 @@ fn permissions_cache() {
|
|||
});
|
||||
}
|
||||
|
||||
itest!(env_file {
|
||||
args: "run --env=env --allow-env run/env_file.ts",
|
||||
output: "run/env_file.out",
|
||||
});
|
||||
|
||||
itest!(env_file_missing {
|
||||
args: "run --env=missing --allow-env run/env_file.ts",
|
||||
output: "run/env_file_missing.out",
|
||||
});
|
||||
|
||||
itest!(lock_write_fetch {
|
||||
args:
|
||||
"run --quiet --allow-import --allow-read --allow-write --allow-env --allow-run run/lock_write_fetch/main.ts",
|
||||
|
|
20
tests/specs/run/env_file/__test__.jsonc
Normal file
20
tests/specs/run/env_file/__test__.jsonc
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"tests": {
|
||||
"basic": {
|
||||
"args": "run --env=./env --allow-env env_file.ts",
|
||||
"output": "env_file.out"
|
||||
},
|
||||
"missing": {
|
||||
"args": "run --env=./missing --allow-env env_file.ts",
|
||||
"output": "env_file_missing.out"
|
||||
},
|
||||
"multiple": {
|
||||
"args": "run --env=./env --env=./env_one --env=./env_two --allow-env env_file.ts",
|
||||
"output": "multiple_env_file.out"
|
||||
},
|
||||
"unparseable": {
|
||||
"args": "run --env=./env_unparseable --allow-env env_file.ts",
|
||||
"output": "env_unparseable.out"
|
||||
}
|
||||
}
|
||||
}
|
4
tests/specs/run/env_file/env
Normal file
4
tests/specs/run/env_file/env
Normal file
|
@ -0,0 +1,4 @@
|
|||
FOO=BAR
|
||||
ANOTHER_FOO=ANOTHER_${FOO}
|
||||
MULTILINE="First Line
|
||||
Second Line"
|
|
@ -1,4 +1,4 @@
|
|||
Warning The `--env-file` flag was used, but the environment file specified 'missing' was not found.
|
||||
Warning The `--env-file` flag was used, but the environment file specified './missing' was not found.
|
||||
undefined
|
||||
undefined
|
||||
undefined
|
2
tests/specs/run/env_file/env_one
Normal file
2
tests/specs/run/env_file/env_one
Normal file
|
@ -0,0 +1,2 @@
|
|||
FOO=BARBAR
|
||||
ANOTHER_FOO=OVERRIDEN_BY_ENV_ONE
|
1
tests/specs/run/env_file/env_two
Normal file
1
tests/specs/run/env_file/env_two
Normal file
|
@ -0,0 +1 @@
|
|||
FOO=OVERRIDEN_BY_ENV_TWO
|
4
tests/specs/run/env_file/env_unparseable.out
Normal file
4
tests/specs/run/env_file/env_unparseable.out
Normal file
|
@ -0,0 +1,4 @@
|
|||
Warning Parsing failed within the specified environment file: ./env_unparseable at index: 3 of the value: c:\path
|
||||
valid
|
||||
undefined
|
||||
undefined
|
4
tests/specs/run/env_file/multiple_env_file.out
Normal file
4
tests/specs/run/env_file/multiple_env_file.out
Normal file
|
@ -0,0 +1,4 @@
|
|||
OVERRIDEN_BY_ENV_TWO
|
||||
OVERRIDEN_BY_ENV_ONE
|
||||
First Line
|
||||
Second Line
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"args": "run --env=../../../testdata/env_unparsable --allow-env main.js",
|
||||
"output": "main.out"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
console.log(Deno.env.get("FOO"));
|
||||
console.log(Deno.env.get("ANOTHER_FOO"));
|
||||
console.log(Deno.env.get("MULTILINE"));
|
|
@ -1,4 +0,0 @@
|
|||
Warning Parsing failed within the specified environment file: ../../../testdata/env_unparsable at index: 3 of the value: c:\path
|
||||
valid
|
||||
undefined
|
||||
undefined
|
|
@ -219,7 +219,7 @@ async function ensureNoNewITests() {
|
|||
"pm_tests.rs": 0,
|
||||
"publish_tests.rs": 0,
|
||||
"repl_tests.rs": 0,
|
||||
"run_tests.rs": 20,
|
||||
"run_tests.rs": 18,
|
||||
"shared_library_tests.rs": 0,
|
||||
"task_tests.rs": 2,
|
||||
"test_tests.rs": 0,
|
||||
|
|
Loading…
Reference in a new issue