1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(unstable): deno run --env (#20300)

This change adds the `--env=[FILE]` flag to the `run`, `compile`,
`eval`, `install` and `repl` subcommands. Environment variables set in
the CLI overwrite those defined in the `.env` file.
This commit is contained in:
Asher Gomez 2023-11-02 02:21:13 +11:00 committed by GitHub
parent 53248e9bb3
commit f8f4e77632
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 4 deletions

7
Cargo.lock generated
View file

@ -1029,6 +1029,7 @@ dependencies = [
"deno_semver",
"deno_task_shell",
"dissimilar",
"dotenvy",
"dprint-plugin-json",
"dprint-plugin-markdown",
"dprint-plugin-typescript",
@ -2085,6 +2086,12 @@ dependencies = [
"syn 0.15.44",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dprint-core"
version = "0.62.1"

View file

@ -76,6 +76,7 @@ dashmap = "5.5.3"
data-encoding.workspace = true
data-url.workspace = true
dissimilar = "=1.0.4"
dotenvy = "0.15.7"
dprint-plugin-json = "=0.19.0"
dprint-plugin-markdown = "=0.16.2"
dprint-plugin-typescript = "=0.88.3"

View file

@ -395,6 +395,7 @@ pub struct Flags {
pub ext: Option<String>,
pub ignore: Vec<PathBuf>,
pub import_map_path: Option<String>,
pub env_file: Option<String>,
pub inspect_brk: Option<SocketAddr>,
pub inspect_wait: Option<SocketAddr>,
pub inspect: Option<SocketAddr>,
@ -1030,6 +1031,7 @@ glob {*_,*.,}bench.{js,mjs,ts,mts,jsx,tsx}:
.arg(watch_arg(false))
.arg(no_clear_screen_arg())
.arg(script_arg().last(true))
.arg(env_file_arg())
})
}
@ -1191,6 +1193,7 @@ supported in canary.
.action(ArgAction::SetTrue),
)
.arg(executable_ext_arg())
.arg(env_file_arg())
.arg(script_arg().required(true).trailing_var_arg(true))
})
}
@ -1434,6 +1437,7 @@ This command has implicit access to all permissions (--allow-all).",
.value_name("CODE_ARG")
.required(true),
)
.arg(env_file_arg())
})
}
@ -1667,6 +1671,7 @@ These must be added to the path manually if required.")
.help("Forcefully overwrite existing installation")
.action(ArgAction::SetTrue))
)
.arg(env_file_arg())
}
fn jupyter_subcommand() -> Command {
@ -1868,6 +1873,7 @@ fn repl_subcommand() -> Command {
.help("Evaluates the provided code when the REPL starts.")
.value_name("code"),
))
.arg(env_file_arg())
}
fn run_subcommand() -> Command {
@ -1882,6 +1888,7 @@ fn run_subcommand() -> Command {
.required_unless_present("v8-flags")
.trailing_var_arg(true),
)
.arg(env_file_arg())
.about("Run a JavaScript or TypeScript program")
.long_about(
"Run a JavaScript or TypeScript program
@ -2063,6 +2070,7 @@ Directory arguments are expanded to all contained files matching the glob
.help("Select reporter to use. Default to 'pretty'.")
.value_parser(["pretty", "dot", "junit", "tap"])
)
.arg(env_file_arg())
)
}
@ -2643,6 +2651,18 @@ fn import_map_arg() -> Arg {
.value_hint(ValueHint::FilePath)
}
fn env_file_arg() -> Arg {
Arg::new("env")
.long("env")
.value_name("FILE")
.help("Load .env file")
.long_help("UNSTABLE: Load environment variables from local file. Only the first environment variable with a given key is used. Existing process environment variables are not overwritten.")
.value_hint(ValueHint::FilePath)
.default_missing_value(".env")
.require_equals(true)
.num_args(0..=1)
}
fn reload_arg() -> Arg {
Arg::new("reload")
.short('r')
@ -3708,6 +3728,7 @@ fn runtime_args_parse(
v8_flags_arg_parse(flags, matches);
seed_arg_parse(flags, matches);
enable_testing_features_arg_parse(flags, matches);
env_file_arg_parse(flags, matches);
}
fn inspect_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
@ -3745,6 +3766,10 @@ fn import_map_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.import_map_path = matches.remove_one::<String>("import-map");
}
fn env_file_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.env_file = matches.remove_one::<String>("env");
}
fn reload_arg_parse(flags: &mut Flags, matches: &mut ArgMatches) {
if let Some(cache_bl) = matches.remove_many::<String>("reload") {
let raw_cache_blocklist: Vec<String> = cache_bl.collect();
@ -5175,7 +5200,7 @@ mod tests {
#[test]
fn eval_with_flags() {
#[rustfmt::skip]
let r = flags_from_vec(svec!["deno", "eval", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "42"]);
let r = flags_from_vec(svec!["deno", "eval", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--env=.example.env", "42"]);
assert_eq!(
r.unwrap(),
Flags {
@ -5204,6 +5229,7 @@ mod tests {
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
env_file: Some(".example.env".to_owned()),
..Flags::default()
}
);
@ -5273,7 +5299,7 @@ mod tests {
#[test]
fn repl_with_flags() {
#[rustfmt::skip]
let r = flags_from_vec(svec!["deno", "repl", "-A", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--unsafely-ignore-certificate-errors"]);
let r = flags_from_vec(svec!["deno", "repl", "-A", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--unsafely-ignore-certificate-errors", "--env=.example.env"]);
assert_eq!(
r.unwrap(),
Flags {
@ -5304,6 +5330,7 @@ mod tests {
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
env_file: Some(".example.env".to_owned()),
unsafely_ignore_certificate_errors: Some(vec![]),
..Flags::default()
}
@ -6067,6 +6094,39 @@ mod tests {
);
}
#[test]
fn run_env_file_default() {
let r = flags_from_vec(svec!["deno", "run", "--env", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
script: "script.ts".to_string(),
watch: Default::default(),
}),
env_file: Some(".env".to_owned()),
..Flags::default()
}
);
}
#[test]
fn run_env_file_defined() {
let r =
flags_from_vec(svec!["deno", "run", "--env=.another_env", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
script: "script.ts".to_string(),
watch: Default::default(),
}),
env_file: Some(".another_env".to_owned()),
..Flags::default()
}
);
}
#[test]
fn cache_multiple() {
let r =
@ -6148,7 +6208,7 @@ mod tests {
#[test]
fn install_with_flags() {
#[rustfmt::skip]
let r = flags_from_vec(svec!["deno", "install", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--name", "file_server", "--root", "/foo", "--force", "https://deno.land/std/http/file_server.ts", "foo", "bar"]);
let r = flags_from_vec(svec!["deno", "install", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--inspect=127.0.0.1:9229", "--name", "file_server", "--root", "/foo", "--force", "--env=.example.env", "https://deno.land/std/http/file_server.ts", "foo", "bar"]);
assert_eq!(
r.unwrap(),
Flags {
@ -6174,6 +6234,7 @@ mod tests {
allow_net: Some(vec![]),
unsafely_ignore_certificate_errors: Some(vec![]),
allow_read: Some(vec![]),
env_file: Some(".example.env".to_owned()),
..Flags::default()
}
);
@ -7557,7 +7618,7 @@ mod tests {
#[test]
fn compile_with_flags() {
#[rustfmt::skip]
let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--output", "colors", "https://deno.land/std/examples/colors.ts", "foo", "bar", "-p", "8080"]);
let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--output", "colors", "--env=.example.env", "https://deno.land/std/examples/colors.ts", "foo", "bar", "-p", "8080"]);
assert_eq!(
r.unwrap(),
Flags {
@ -7584,6 +7645,7 @@ mod tests {
allow_net: Some(vec![]),
v8_flags: svec!["--help", "--random-seed=1"],
seed: Some(1),
env_file: Some(".example.env".to_owned()),
..Flags::default()
}
);

View file

@ -52,6 +52,7 @@ use deno_runtime::deno_tls::rustls_pemfile;
use deno_runtime::deno_tls::webpki_roots;
use deno_runtime::inspector_server::InspectorServer;
use deno_runtime::permissions::PermissionsOptions;
use dotenvy::from_filename;
use once_cell::sync::Lazy;
use once_cell::sync::OnceCell;
use serde::Deserialize;
@ -651,6 +652,12 @@ impl CliOptions {
let maybe_vendor_folder =
resolve_vendor_folder(&initial_cwd, &flags, maybe_config_file.as_ref());
if let Some(env_file_name) = &flags.env_file {
if (from_filename(env_file_name)).is_err() {
bail!("Unable to load '{env_file_name}' environment variable file")
}
}
Ok(Self {
flags,
initial_cwd,

View file

@ -77,3 +77,16 @@ itest!(check_local_by_default2 {
output: "eval/check_local_by_default2.out",
http_server: true,
});
itest!(env_file {
args: "eval --env=env console.log(Deno.env.get(\"ANOTHER_FOO\"))",
output_str: Some("ANOTHER_BAR\n"),
});
itest!(env_file_missing {
args: "eval --env=missing console.log(Deno.env.get(\"ANOTHER_FOO\"))",
output_str: Some(
"error: Unable to load 'missing' environment variable file\n"
),
exit_code: 1,
});

View file

@ -1058,3 +1058,19 @@ fn closed_file_pre_load_does_not_occur() {
assert_contains!(console.all_output(), "Skipping document preload.",);
});
}
#[test]
fn env_file() {
TestContext::default()
.new_command()
.args_vec([
"repl",
"--env=env",
"--allow-env",
"--eval",
"console.log(Deno.env.get('FOO'))",
])
.with_pty(|console| {
assert_contains!(console.all_output(), "BAR",);
});
}

View file

@ -808,6 +808,19 @@ 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_str: Some(
"error: Unable to load 'missing' environment variable file\n"
),
exit_code: 1,
});
itest!(_091_use_define_for_class_fields {
args: "run --check run/091_use_define_for_class_fields.ts",
output: "run/091_use_define_for_class_fields.ts.out",

4
cli/tests/testdata/env vendored Normal file
View file

@ -0,0 +1,4 @@
FOO=BAR
ANOTHER_FOO=ANOTHER_${FOO}
MULTILINE="First Line
Second Line"

4
cli/tests/testdata/run/env_file.out vendored Normal file
View file

@ -0,0 +1,4 @@
BAR
ANOTHER_BAR
First Line
Second Line

3
cli/tests/testdata/run/env_file.ts vendored Normal file
View file

@ -0,0 +1,3 @@
console.log(Deno.env.get("FOO"));
console.log(Deno.env.get("ANOTHER_FOO"));
console.log(Deno.env.get("MULTILINE"));