Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:47:45.008Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

yarn.rs

139 lines · 4.9 KB · rust
1//! This module deals with everything related to path handling for Yarn, the package manager for Web ecosystem.
2//! Yarn is a bit peculiar, because it references paths within .zip files, which we obviously can't handle.
3//! It also uses virtual paths for peer dependencies.
4//!
5//! Long story short, before we attempt to resolve a path as a "real" path, we try to treat is as a yarn path;
6//! for .zip handling, we unpack the contents into the temp directory (yes, this is bad, against the spirit of Yarn and what-not)
7
8use std::{
9    ffi::OsStr,
10    path::{Path, PathBuf},
11    sync::Arc,
12};
13
14use anyhow::Result;
15use collections::HashMap;
16use fs::Fs;
17use gpui::{App, AppContext as _, Context, Entity, Task};
18use util::{ResultExt, archive::extract_zip, paths::PathStyle, rel_path::RelPath};
19
20pub(crate) struct YarnPathStore {
21    temp_dirs: HashMap<Arc<Path>, tempfile::TempDir>,
22    fs: Arc<dyn Fs>,
23}
24
25/// Returns `None` when passed path is a malformed virtual path or it's not a virtual path at all.
26pub fn resolve_virtual(path: &Path) -> Option<Arc<Path>> {
27    let components: Vec<_> = path.components().collect();
28    let mut non_virtual_path = PathBuf::new();
29
30    let mut i = 0;
31    let mut is_virtual = false;
32    while i < components.len() {
33        if let Some(os_str) = components[i].as_os_str().to_str() {
34            // Detect the __virtual__ segment
35            if os_str == "__virtual__" {
36                let pop_count = components
37                    .get(i + 2)?
38                    .as_os_str()
39                    .to_str()?
40                    .parse::<usize>()
41                    .ok()?;
42
43                // Apply dirname operation pop_count times
44                for _ in 0..pop_count {
45                    non_virtual_path.pop();
46                }
47                i += 3; // Skip hash and pop_count components
48                is_virtual = true;
49                continue;
50            }
51        }
52        non_virtual_path.push(components[i]);
53        i += 1;
54    }
55
56    is_virtual.then(|| Arc::from(non_virtual_path))
57}
58
59impl YarnPathStore {
60    pub(crate) fn new(fs: Arc<dyn Fs>, cx: &mut App) -> Entity<Self> {
61        cx.new(|_| Self {
62            temp_dirs: Default::default(),
63            fs,
64        })
65    }
66
67    pub(crate) fn process_path(
68        &mut self,
69        path: &Path,
70        protocol: &str,
71        cx: &Context<Self>,
72    ) -> Task<Option<(Arc<Path>, Arc<RelPath>)>> {
73        let mut is_zip = protocol.eq("zip");
74
75        let path: &Path = if let Some(non_zip_part) = path
76            .as_os_str()
77            .as_encoded_bytes()
78            .strip_prefix("/zip:".as_bytes())
79        {
80            // typescript-language-server prepends the paths with zip:, which is messy.
81            is_zip = true;
82            Path::new(OsStr::new(
83                std::str::from_utf8(non_zip_part).expect("Invalid UTF-8"),
84            ))
85        } else {
86            path
87        };
88
89        let as_virtual = resolve_virtual(path);
90        let Some(path) = as_virtual.or_else(|| is_zip.then(|| Arc::from(path))) else {
91            return Task::ready(None);
92        };
93        if let Some(zip_file) = zip_path(&path) {
94            let zip_file: Arc<Path> = Arc::from(zip_file);
95            cx.spawn(async move |this, cx| {
96                let dir = this
97                    .read_with(cx, |this, _| {
98                        this.temp_dirs
99                            .get(&zip_file)
100                            .map(|temp| temp.path().to_owned())
101                    })
102                    .ok()?;
103                let zip_root = if let Some(dir) = dir {
104                    dir
105                } else {
106                    let fs = this.update(cx, |this, _| this.fs.clone()).ok()?;
107                    let tempdir = dump_zip(zip_file.clone(), fs).await.log_err()?;
108                    let new_path = tempdir.path().to_owned();
109                    this.update(cx, |this, _| {
110                        this.temp_dirs.insert(zip_file.clone(), tempdir);
111                    })
112                    .ok()?;
113                    new_path
114                };
115                // Rebase zip-path onto new temp path.
116                let as_relative =
117                    RelPath::new(path.strip_prefix(zip_file).ok()?, PathStyle::local()).ok()?;
118                Some((zip_root.into(), as_relative.into_arc()))
119            })
120        } else {
121            Task::ready(None)
122        }
123    }
124}
125
126fn zip_path(path: &Path) -> Option<&Path> {
127    let path_str = path.to_str()?;
128    let zip_end = path_str.find(".zip/")?;
129    let zip_path = &path_str[..zip_end + 4]; // ".zip" is 4 characters long
130    Some(Path::new(zip_path))
131}
132
133async fn dump_zip(path: Arc<Path>, fs: Arc<dyn Fs>) -> Result<tempfile::TempDir> {
134    let dir = tempfile::tempdir()?;
135    let contents = fs.load_bytes(&path).await?;
136    extract_zip(dir.path(), futures::io::Cursor::new(contents)).await?;
137    Ok(dir)
138}
139
Served at tenant.openagents/omega Member data and write actions are omitted.