Skip to main content

paralock_cli/
state.rs

1//! Where the local state lives (the rule of the TypeScript SDK's `state-dir.ts`), and the user's home directory.
2
3use std::path::{Path, PathBuf};
4
5/// Both exist at the root of a Paralock checkout, never in an install of the CLI.
6const CHECKOUT_MARKERS: [&str; 2] = ["pnpm-workspace.yaml", "contracts/src/ParalockHTLC.sol"];
7
8/// The root of the Paralock checkout containing `start` (the nearest directory, `start` included, that holds both
9/// `pnpm-workspace.yaml` and `contracts/src/ParalockHTLC.sol`), if any.
10pub fn find_checkout(start: &Path) -> Option<PathBuf> {
11    start.ancestors().find(|dir| CHECKOUT_MARKERS.iter().all(|marker| dir.join(marker).exists())).map(Path::to_path_buf)
12}
13
14/// The user's home directory (`$HOME`, or `%USERPROFILE%` on Windows).
15pub fn home_dir() -> Option<PathBuf> {
16    let var = if cfg!(windows) { "USERPROFILE" } else { "HOME" };
17    std::env::var_os(var).filter(|v| !v.is_empty()).map(PathBuf::from)
18}
19
20/// Where Paralock keeps the local devnet's state (`devnet.json`, swap stores, keys):
21///
22/// 1. inside a Paralock checkout: `<checkout>/.paralock`, whatever `PARALOCK_HOME` says;
23/// 2. otherwise `$PARALOCK_HOME` (relative to `cwd`), when set and not empty;
24/// 3. otherwise `~/.paralock`.
25pub fn state_dir(cwd: &Path, paralock_home: Option<&str>) -> PathBuf {
26    if let Some(checkout) = find_checkout(cwd) {
27        return checkout.join(".paralock");
28    }
29    match paralock_home.map(str::trim).filter(|h| !h.is_empty()) {
30        Some(home) => cwd.join(home),
31        None => home_dir().unwrap_or_else(|| cwd.to_path_buf()).join(".paralock"),
32    }
33}
34
35/// [`state_dir`] for this process (current directory and environment).
36pub fn current_state_dir() -> PathBuf {
37    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
38    state_dir(&cwd, std::env::var("PARALOCK_HOME").ok().as_deref())
39}
40
41/// Expands a leading `~/` and resolves a relative path against `base`.
42pub fn resolve_path(path: &str, base: &Path) -> PathBuf {
43    let expanded = match path.strip_prefix("~/") {
44        Some(rest) => home_dir().map_or_else(|| PathBuf::from(path), |home| home.join(rest)),
45        None if path == "~" => home_dir().unwrap_or_else(|| PathBuf::from(path)),
46        None => PathBuf::from(path),
47    };
48    if expanded.is_absolute() { expanded } else { base.join(expanded) }
49}