Skip to main content

turbo_tasks_env/
dotenv.rs

1use std::{env, sync::MutexGuard};
2
3use anyhow::{Context, Result};
4use turbo_rcstr::RcStr;
5use turbo_tasks::{FxIndexMap, ReadRef, ResolvedVc, Vc, turbofmt};
6use turbo_tasks_fs::{FileContent, FileSystemPath};
7
8use crate::{GLOBAL_ENV_LOCK, ProcessEnv, TransientEnvMap, sorted_env_vars};
9
10/// Load the environment variables defined via a dotenv file, with an
11/// optional prior state that we can lookup already defined variables
12/// from.
13#[turbo_tasks::value]
14pub struct DotenvProcessEnv {
15    prior: Option<ResolvedVc<Box<dyn ProcessEnv>>>,
16    path: FileSystemPath,
17}
18
19#[turbo_tasks::value_impl]
20impl DotenvProcessEnv {
21    #[turbo_tasks::function]
22    pub fn new(prior: Option<ResolvedVc<Box<dyn ProcessEnv>>>, path: FileSystemPath) -> Vc<Self> {
23        DotenvProcessEnv { prior, path }.cell()
24    }
25
26    #[turbo_tasks::function]
27    pub fn read_prior(&self) -> Vc<TransientEnvMap> {
28        match self.prior {
29            None => TransientEnvMap::empty(),
30            Some(p) => p.read_all(),
31        }
32    }
33
34    #[turbo_tasks::function]
35    pub async fn read_all_with_prior(
36        &self,
37        prior: Vc<TransientEnvMap>,
38    ) -> Result<Vc<TransientEnvMap>> {
39        let prior = prior.await?;
40
41        let file = self.path.read().await?;
42        if let FileContent::Content(f) = &*file {
43            let res;
44            let vars;
45            {
46                let lock = GLOBAL_ENV_LOCK.lock().unwrap();
47
48                // Unfortunately, dotenvy only looks up variable references from the global env.
49                // So we must mutate while we process. Afterwards, we can restore the initial
50                // state.
51                let initial = sorted_env_vars();
52
53                restore_env(&initial, &prior, &lock);
54
55                // from_read will load parse and evaluate the Read, and set variables
56                // into the global env. If a later dotenv defines an already defined
57                // var, it'll be ignored.
58                res = dotenv::from_read(f.read()).map(|e| e.load());
59
60                vars = sorted_env_vars();
61                restore_env(&vars, &initial, &lock);
62            }
63
64            if let Err(e) = res {
65                // ast-grep-ignore: no-context-turbofmt
66                return Err(e)
67                    .context(turbofmt!("unable to read {} for env vars", self.path).await?);
68            }
69
70            Ok(Vc::cell(vars))
71        } else {
72            // We want to cell the value here and not just return the Vc.
73            // This is important to avoid Vc changes when adding/removing the env file.
74            Ok(ReadRef::cell(prior))
75        }
76    }
77}
78
79#[turbo_tasks::value_impl]
80impl ProcessEnv for DotenvProcessEnv {
81    #[turbo_tasks::function]
82    fn read_all(self: Vc<Self>) -> Vc<TransientEnvMap> {
83        let prior = self.read_prior();
84        self.read_all_with_prior(prior)
85    }
86}
87
88/// Restores the global env variables to mirror `to`.
89fn restore_env(
90    from: &FxIndexMap<RcStr, RcStr>,
91    to: &FxIndexMap<RcStr, RcStr>,
92    _lock: &MutexGuard<()>,
93) {
94    for key in from.keys() {
95        if !to.contains_key(key) {
96            unsafe { env::remove_var(key) };
97        }
98    }
99    for (key, value) in to {
100        match from.get(key) {
101            Some(v) if v == value => {}
102            _ => unsafe { env::set_var(key, value) },
103        }
104    }
105}