deno 2.8.3

Provides the deno executable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
// Copyright 2018-2026 the Deno authors. MIT license.

use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::OnceLock;

use deno_terminal::colors;

use crate::sys::CliSys;

/// Environment variables that configure Deno's own runtime behavior and must
/// not be settable from an `.env` file loaded via `--env` / `--env-file`.
///
/// An `.env` file ships alongside the code it accompanies, so letting it set
/// Deno's own control variables would let that file change runtime behavior
/// the user did not opt into, for example silently enabling tunnel mode
/// (`DENO_CONNECTED`) or overriding the tunnel control endpoint
/// (`DENO_DEPLOY_TUNNEL_ENDPOINT`). Env files are meant to provide
/// configuration to the user's program, not to reconfigure Deno itself, so
/// these keys are ignored when they originate from an env file.
const ENV_FILE_DENYLIST: &[&str] =
  &["DENO_CONNECTED", "DENO_DEPLOY_TUNNEL_ENDPOINT"];

/// Whether `key` is a Deno-internal control variable that must not be set from
/// an env file. The comparison is ASCII case-insensitive because environment
/// variable lookups are case-insensitive on Windows.
fn is_denied_env_file_key(key: &OsStr) -> bool {
  match key.to_str() {
    Some(key) => ENV_FILE_DENYLIST
      .iter()
      .any(|denied| key.eq_ignore_ascii_case(denied)),
    None => false,
  }
}

pub fn handle_denied_env_file_key(
  key: &OsStr,
  file_path: &Path,
  log_level: Option<log::Level>,
) {
  #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
  if log_level.map(|l| l >= log::Level::Info).unwrap_or(true) {
    eprintln!(
      "{} Ignoring '{}' from environment file '{}': this variable controls Deno's own runtime behavior and cannot be set from an env file.",
      colors::yellow("Warning"),
      key.to_string_lossy(),
      file_path.display(),
    )
  }
}

pub fn resolve_cwd(
  initial_cwd: Option<&Path>,
) -> Result<Cow<'_, Path>, std::io::Error> {
  match initial_cwd {
    Some(initial_cwd) => Ok(Cow::Borrowed(initial_cwd)),
    #[allow(
      clippy::disallowed_methods,
      reason = "ok because the lint recommends using this method"
    )]
    None => std::env::current_dir().map(Cow::Owned).map_err(|err| {
      std::io::Error::new(
        err.kind(),
        format!("could not read current working directory: {err}"),
      )
    }),
  }
}

/// Like `resolve_cwd`, but falls back to a sensible default (the system
/// root) when the current working directory can't be determined — for
/// example when it has been unlinked. This matches Node.js semantics where
/// the REPL still starts even if the parent process's cwd was deleted.
pub fn resolve_cwd_or_fallback(initial_cwd: Option<&Path>) -> PathBuf {
  match resolve_cwd(initial_cwd) {
    Ok(cwd) => cwd.into_owned(),
    Err(_) => fallback_cwd(),
  }
}

fn fallback_cwd() -> PathBuf {
  if cfg!(windows) {
    // System drive root, e.g. `C:\`.
    std::env::var_os("SystemDrive")
      .map(|d| {
        let mut p = PathBuf::from(d);
        p.push("\\");
        p
      })
      .unwrap_or_else(|| PathBuf::from("C:\\"))
  } else {
    PathBuf::from("/")
  }
}

#[derive(Debug, Clone)]
struct WatchEnvTrackerInner {
  // Track all loaded variables and their values
  loaded_variables: HashSet<OsString>,
  // Track variables that are no longer present in any loaded file
  unused_variables: HashSet<OsString>,
  // Track original env vars that existed before we started
  original_env: HashMap<OsString, OsString>,
}

impl WatchEnvTrackerInner {
  fn new() -> Self {
    // Capture the original environment state
    let original_env: HashMap<OsString, OsString> = env::vars_os().collect();

    Self {
      loaded_variables: Default::default(),
      unused_variables: Default::default(),
      original_env,
    }
  }
}

#[derive(Debug, Clone)]
pub struct WatchEnvTracker {
  inner: Arc<Mutex<WatchEnvTrackerInner>>,
}

// Global singleton instance
static WATCH_ENV_TRACKER: OnceLock<WatchEnvTracker> = OnceLock::new();

impl WatchEnvTracker {
  /// Get the global singleton instance
  pub fn snapshot() -> &'static WatchEnvTracker {
    WATCH_ENV_TRACKER.get_or_init(|| WatchEnvTracker {
      inner: Arc::new(Mutex::new(WatchEnvTrackerInner::new())),
    })
  }

  // Internal method that accepts an already-acquired lock to avoid deadlocks
  fn load_env_file_inner(
    &self,
    cwd: &Path,
    env_file: &str,
    log_level: Option<log::Level>,
    inner: &mut WatchEnvTrackerInner,
  ) {
    let (file_path, content) = match deno_dotenv::find_path_and_content(
      &CliSys::default(),
      cwd,
      env_file,
    ) {
      Ok(Some(result)) => result,
      Ok(None) => {
        handle_dotenv_not_found(env_file, log_level);
        return;
      }
      Err(err) => {
        handle_dotenv_io_error(&err, log_level);
        return;
      }
    };

    match deno_dotenv::from_content_sanitized_iter_with_substitution(
      &CliSys::default(),
      &content,
    ) {
      Ok(iter) => {
        for item in iter {
          match item {
            Ok((key, value)) => {
              // Convert to OsString for consistency
              let key_os = OsString::from(key);
              let value_os = OsString::from(value);

              // Process-level env vars should always take precedence over env files.
              if inner.original_env.contains_key(&key_os) {
                #[allow(
                  clippy::print_stderr,
                  reason = "can't use log crate yet"
                )]
                if log_level.map(|l| l >= log::Level::Debug).unwrap_or(false) {
                  eprintln!(
                    "{} Variable '{}' already exists in the process environment, skipping value from '{}'",
                    colors::yellow("Debug"),
                    key_os.to_string_lossy(),
                    file_path.display()
                  );
                }
                continue;
              }

              // Check if this variable is already loaded from a previous file
              if inner.loaded_variables.contains(&key_os) {
                // Variable already exists from a previous file, skip it
                #[allow(
                  clippy::print_stderr,
                  reason = "can't use log crate yet"
                )]
                if log_level.map(|l| l >= log::Level::Debug).unwrap_or(false) {
                  eprintln!(
                    "{} Variable '{}' already loaded from '{}', skipping value from '{}'",
                    colors::yellow("Debug"),
                    key_os.to_string_lossy(),
                    inner
                      .loaded_variables
                      .get(&key_os)
                      .map(|k| k.to_string_lossy().to_string())
                      .unwrap_or_else(|| "unknown".to_string()),
                    file_path.display()
                  );
                }
                continue;
              }

              if is_denied_env_file_key(&key_os) {
                handle_denied_env_file_key(&key_os, &file_path, log_level);
                continue;
              }

              // Set the environment variable
              // SAFETY: We're setting environment variables with valid UTF-8 strings
              // from the .env file. Both key and value are guaranteed to be valid strings.
              unsafe {
                env::set_var(&key_os, &value_os);
              }

              // Track this variable
              inner.loaded_variables.insert(key_os.clone());
              inner.unused_variables.remove(&key_os);
            }
            Err(e) => {
              handle_dotenv_error(&e, &file_path, log_level);
            }
          }
        }
      }
      Err(e) => {
        handle_dotenv_error(&e, &file_path, log_level);
      }
    }
  }

  /// Clean up variables that are no longer present in any loaded file
  fn _cleanup_removed_variables(
    &self,
    inner: &mut WatchEnvTrackerInner,
    log_level: Option<log::Level>,
  ) {
    for var_name in inner.unused_variables.iter() {
      if !inner.original_env.contains_key(var_name) {
        // SAFETY: We're removing an environment variable that we previously set
        unsafe {
          env::remove_var(var_name);
        }

        #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
        if log_level.map(|l| l >= log::Level::Debug).unwrap_or(false) {
          eprintln!(
            "{} Variable '{}' removed from environment as it's no longer present in any loaded file",
            colors::yellow("Debug"),
            var_name.to_string_lossy()
          );
        }
      } else {
        let original_value = inner.original_env.get(var_name).unwrap();
        // SAFETY: We're setting an environment variable to a value we control
        unsafe {
          env::set_var(var_name, original_value);
        }

        #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
        if log_level.map(|l| l >= log::Level::Debug).unwrap_or(false) {
          eprintln!(
            "{} Variable '{}' restored to original value as it's no longer present in any loaded file",
            colors::yellow("Debug"),
            var_name.to_string_lossy()
          );
        }
      }
    }
  }

  // Load multiple env files in reverse order (later files take precedence over earlier ones)
  pub fn load_env_variables_from_env_files(
    &self,
    cwd: &Path,
    env_files: &[String],
    log_level: Option<log::Level>,
  ) {
    let mut inner = self.inner.lock().unwrap();

    inner.unused_variables = std::mem::take(&mut inner.loaded_variables);
    inner.loaded_variables = HashSet::new();

    for env_file_path in env_files.iter().rev() {
      self.load_env_file_inner(cwd, env_file_path, log_level, &mut inner);
    }

    self._cleanup_removed_variables(&mut inner, log_level);
  }
}

pub fn load_env_variables_from_env_files(
  cwd: &Path,
  env_file_names: &[String],
  flags_log_level: Option<log::Level>,
) {
  let original_env_keys: HashSet<OsString> =
    env::vars_os().map(|(key, _)| key).collect();
  let mut loaded_keys = HashSet::new();

  for env_file_name in env_file_names.iter().rev() {
    let (env_file_path, content) = match deno_dotenv::find_path_and_content(
      &CliSys::default(),
      cwd,
      env_file_name,
    ) {
      Ok(Some(resolved)) => resolved,
      Ok(None) => {
        handle_dotenv_not_found(env_file_name, flags_log_level);
        continue;
      }
      Err(err) => {
        handle_dotenv_io_error(&err, flags_log_level);
        continue;
      }
    };
    let iter = match deno_dotenv::from_content_sanitized_iter_with_substitution(
      &sys_traits::impls::RealSys,
      &content,
    ) {
      Ok(iter) => iter,
      Err(err) => {
        handle_dotenv_error(&err, &env_file_path, flags_log_level);
        continue;
      }
    };

    for item in iter {
      let (key, value) = match item {
        Ok(pair) => pair,
        Err(error) => {
          handle_dotenv_error(&error, &env_file_path, flags_log_level);
          break;
        }
      };

      let key_os = OsString::from(key);
      if original_env_keys.contains(&key_os) || loaded_keys.contains(&key_os) {
        continue;
      }

      if is_denied_env_file_key(&key_os) {
        handle_denied_env_file_key(&key_os, &env_file_path, flags_log_level);
        continue;
      }

      // SAFETY: We're setting environment variables with sanitized key/value strings from a .env file.
      unsafe {
        env::set_var(&key_os, value);
      }
      loaded_keys.insert(key_os);
    }
  }
}

pub fn handle_dotenv_error(
  error: &deno_dotenv::ParseError,
  file_path: &Path,
  log_level: Option<log::Level>,
) {
  #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
  if log_level.map(|l| l >= log::Level::Info).unwrap_or(true) {
    eprintln!(
      "{} Failed parsing value '{}' at index {} within the specified environment file.\n    at {}",
      colors::yellow("Warning"),
      error.line,
      error.index,
      file_path.display(),
    )
  }
}

pub fn handle_dotenv_io_error(
  error: &deno_dotenv::FindPathAndContentError,
  log_level: Option<log::Level>,
) {
  #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
  if log_level.map(|l| l >= log::Level::Info).unwrap_or(true) {
    eprintln!(
      "{} Error reading from environment file: {}\n    at {}",
      colors::yellow("Warning"),
      error.source,
      error.path.display(),
    )
  }
}

pub fn handle_dotenv_not_found(specifier: &str, log_level: Option<log::Level>) {
  #[allow(clippy::print_stderr, reason = "can't use log crate yet")]
  if log_level.map(|l| l >= log::Level::Info).unwrap_or(true) {
    eprintln!(
      "{} The `--env-file` flag was used, but the environment file specified '{}' was not found.",
      colors::yellow("Warning"),
      specifier,
    )
  }
}