spacetimedb_paths/
cli.rs

1use std::path::Path;
2
3use anyhow::Context;
4
5use crate::utils::{path_type, PathBufExt};
6
7path_type! {
8    /// The configuration directory for the CLI & keyfiles.
9    ConfigDir: dir
10}
11
12impl ConfigDir {
13    pub fn jwt_priv_key(&self) -> PrivKeyPath {
14        PrivKeyPath(self.0.join("id_ecdsa"))
15    }
16    pub fn jwt_pub_key(&self) -> PubKeyPath {
17        PubKeyPath(self.0.join("id_ecdsa.pub"))
18    }
19    pub fn cli_toml(&self) -> CliTomlPath {
20        CliTomlPath(self.0.join("cli.toml"))
21    }
22}
23
24// TODO: replace cfg(any()) with cfg(false) once stabilized
25path_type!(#[non_exhaustive(any())] PrivKeyPath: file);
26path_type!(#[non_exhaustive(any())] PubKeyPath: file);
27
28path_type!(CliTomlPath: file);
29
30path_type!(BinFile: file);
31
32path_type!(BinDir: dir);
33
34impl BinDir {
35    pub fn version_dir(&self, version: &str) -> VersionBinDir {
36        VersionBinDir(self.0.join(version))
37    }
38
39    pub const CURRENT_VERSION_DIR_NAME: &str = "current";
40    pub fn current_version_dir(&self) -> VersionBinDir {
41        VersionBinDir(self.0.join(Self::CURRENT_VERSION_DIR_NAME))
42    }
43
44    pub fn set_current_version(&self, version: &str) -> anyhow::Result<()> {
45        self.current_version_dir().link_to(self.version_dir(version).as_ref())
46    }
47
48    pub fn current_version(&self) -> anyhow::Result<Option<String>> {
49        match std::fs::read_link(self.current_version_dir()) {
50            Ok(path) => path.into_os_string().into_string().ok().context("not utf8").map(Some),
51            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
52            Err(e) => Err(e.into()),
53        }
54    }
55
56    pub fn installed_versions(&self) -> anyhow::Result<Vec<String>> {
57        self.read_dir()?
58            .filter_map(|r| match r {
59                Ok(entry) => {
60                    let name = entry.file_name();
61                    if name == Self::CURRENT_VERSION_DIR_NAME {
62                        None
63                    } else {
64                        entry.file_name().into_string().ok().map(Ok)
65                    }
66                }
67                Err(e) => Some(Err(e.into())),
68            })
69            .collect()
70    }
71}
72
73path_type!(VersionBinDir: dir);
74
75impl VersionBinDir {
76    pub fn spacetimedb_cli(self) -> SpacetimedbCliBin {
77        SpacetimedbCliBin(self.0.joined("spacetimedb-cli").with_exe_ext())
78    }
79
80    pub fn create_custom(&self, path: &Path) -> anyhow::Result<()> {
81        if std::fs::symlink_metadata(self).is_ok_and(|m| m.file_type().is_dir()) {
82            anyhow::bail!("version already exists");
83        }
84        self.link_to(path)
85    }
86
87    fn link_to(&self, path: &Path) -> anyhow::Result<()> {
88        let rel_path = path.strip_prefix(self.0.parent().unwrap()).unwrap_or(path);
89        #[cfg(unix)]
90        {
91            // remove the link if it already exists
92            std::fs::remove_file(self).ok();
93            std::os::unix::fs::symlink(rel_path, self)?;
94        }
95        #[cfg(windows)]
96        {
97            junction::delete(self).ok();
98            std::fs::remove_dir(self).ok();
99            // We won't be able to create a junction if the fs isn't NTFS, so fall back to trying
100            // to make a symlink.
101            junction::create(path, self)
102                .or_else(|err| std::os::windows::fs::symlink_dir(rel_path, self).or(Err(err)))?;
103        }
104        Ok(())
105    }
106}
107
108path_type!(SpacetimedbCliBin: file);