since_v0_5_0.rs

  1use crate::wasm_host::wit::since_v0_5_0::slash_command::SlashCommandOutputSection;
  2use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
  3use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
  4use ::http_client::{AsyncBody, HttpRequestExt};
  5use ::settings::{Settings, WorktreeId};
  6use anyhow::{Context, Result, anyhow, bail};
  7use async_compression::futures::bufread::GzipDecoder;
  8use async_tar::Archive;
  9use async_trait::async_trait;
 10use context_server_settings::ContextServerSettings;
 11use extension::{
 12    ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
 13};
 14use futures::{AsyncReadExt, lock::Mutex};
 15use futures::{FutureExt as _, io::BufReader};
 16use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
 17use project::project_settings::ProjectSettings;
 18use semantic_version::SemanticVersion;
 19use std::{
 20    env,
 21    path::{Path, PathBuf},
 22    sync::{Arc, OnceLock},
 23};
 24use util::maybe;
 25use wasmtime::component::{Linker, Resource};
 26
 27pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 5, 0);
 28pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 5, 0);
 29
 30wasmtime::component::bindgen!({
 31    async: true,
 32    trappable_imports: true,
 33    path: "../extension_api/wit/since_v0.5.0",
 34    with: {
 35         "worktree": ExtensionWorktree,
 36         "project": ExtensionProject,
 37         "key-value-store": ExtensionKeyValueStore,
 38         "zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
 39    },
 40});
 41
 42pub use self::zed::extension::*;
 43
 44mod settings {
 45    include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs"));
 46}
 47
 48pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
 49pub type ExtensionProject = Arc<dyn ProjectDelegate>;
 50pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
 51pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
 52
 53pub fn linker() -> &'static Linker<WasmState> {
 54    static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
 55    LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
 56}
 57
 58impl From<Range> for std::ops::Range<usize> {
 59    fn from(range: Range) -> Self {
 60        let start = range.start as usize;
 61        let end = range.end as usize;
 62        start..end
 63    }
 64}
 65
 66impl From<Command> for extension::Command {
 67    fn from(value: Command) -> Self {
 68        Self {
 69            command: value.command,
 70            args: value.args,
 71            env: value.env,
 72        }
 73    }
 74}
 75
 76impl From<CodeLabel> for extension::CodeLabel {
 77    fn from(value: CodeLabel) -> Self {
 78        Self {
 79            code: value.code,
 80            spans: value.spans.into_iter().map(Into::into).collect(),
 81            filter_range: value.filter_range.into(),
 82        }
 83    }
 84}
 85
 86impl From<CodeLabelSpan> for extension::CodeLabelSpan {
 87    fn from(value: CodeLabelSpan) -> Self {
 88        match value {
 89            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
 90            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
 91        }
 92    }
 93}
 94
 95impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
 96    fn from(value: CodeLabelSpanLiteral) -> Self {
 97        Self {
 98            text: value.text,
 99            highlight_name: value.highlight_name,
100        }
101    }
102}
103
104impl From<extension::Completion> for Completion {
105    fn from(value: extension::Completion) -> Self {
106        Self {
107            label: value.label,
108            label_details: value.label_details.map(Into::into),
109            detail: value.detail,
110            kind: value.kind.map(Into::into),
111            insert_text_format: value.insert_text_format.map(Into::into),
112        }
113    }
114}
115
116impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
117    fn from(value: extension::CompletionLabelDetails) -> Self {
118        Self {
119            detail: value.detail,
120            description: value.description,
121        }
122    }
123}
124
125impl From<extension::CompletionKind> for CompletionKind {
126    fn from(value: extension::CompletionKind) -> Self {
127        match value {
128            extension::CompletionKind::Text => Self::Text,
129            extension::CompletionKind::Method => Self::Method,
130            extension::CompletionKind::Function => Self::Function,
131            extension::CompletionKind::Constructor => Self::Constructor,
132            extension::CompletionKind::Field => Self::Field,
133            extension::CompletionKind::Variable => Self::Variable,
134            extension::CompletionKind::Class => Self::Class,
135            extension::CompletionKind::Interface => Self::Interface,
136            extension::CompletionKind::Module => Self::Module,
137            extension::CompletionKind::Property => Self::Property,
138            extension::CompletionKind::Unit => Self::Unit,
139            extension::CompletionKind::Value => Self::Value,
140            extension::CompletionKind::Enum => Self::Enum,
141            extension::CompletionKind::Keyword => Self::Keyword,
142            extension::CompletionKind::Snippet => Self::Snippet,
143            extension::CompletionKind::Color => Self::Color,
144            extension::CompletionKind::File => Self::File,
145            extension::CompletionKind::Reference => Self::Reference,
146            extension::CompletionKind::Folder => Self::Folder,
147            extension::CompletionKind::EnumMember => Self::EnumMember,
148            extension::CompletionKind::Constant => Self::Constant,
149            extension::CompletionKind::Struct => Self::Struct,
150            extension::CompletionKind::Event => Self::Event,
151            extension::CompletionKind::Operator => Self::Operator,
152            extension::CompletionKind::TypeParameter => Self::TypeParameter,
153            extension::CompletionKind::Other(value) => Self::Other(value),
154        }
155    }
156}
157
158impl From<extension::InsertTextFormat> for InsertTextFormat {
159    fn from(value: extension::InsertTextFormat) -> Self {
160        match value {
161            extension::InsertTextFormat::PlainText => Self::PlainText,
162            extension::InsertTextFormat::Snippet => Self::Snippet,
163            extension::InsertTextFormat::Other(value) => Self::Other(value),
164        }
165    }
166}
167
168impl From<extension::Symbol> for Symbol {
169    fn from(value: extension::Symbol) -> Self {
170        Self {
171            kind: value.kind.into(),
172            name: value.name,
173        }
174    }
175}
176
177impl From<extension::SymbolKind> for SymbolKind {
178    fn from(value: extension::SymbolKind) -> Self {
179        match value {
180            extension::SymbolKind::File => Self::File,
181            extension::SymbolKind::Module => Self::Module,
182            extension::SymbolKind::Namespace => Self::Namespace,
183            extension::SymbolKind::Package => Self::Package,
184            extension::SymbolKind::Class => Self::Class,
185            extension::SymbolKind::Method => Self::Method,
186            extension::SymbolKind::Property => Self::Property,
187            extension::SymbolKind::Field => Self::Field,
188            extension::SymbolKind::Constructor => Self::Constructor,
189            extension::SymbolKind::Enum => Self::Enum,
190            extension::SymbolKind::Interface => Self::Interface,
191            extension::SymbolKind::Function => Self::Function,
192            extension::SymbolKind::Variable => Self::Variable,
193            extension::SymbolKind::Constant => Self::Constant,
194            extension::SymbolKind::String => Self::String,
195            extension::SymbolKind::Number => Self::Number,
196            extension::SymbolKind::Boolean => Self::Boolean,
197            extension::SymbolKind::Array => Self::Array,
198            extension::SymbolKind::Object => Self::Object,
199            extension::SymbolKind::Key => Self::Key,
200            extension::SymbolKind::Null => Self::Null,
201            extension::SymbolKind::EnumMember => Self::EnumMember,
202            extension::SymbolKind::Struct => Self::Struct,
203            extension::SymbolKind::Event => Self::Event,
204            extension::SymbolKind::Operator => Self::Operator,
205            extension::SymbolKind::TypeParameter => Self::TypeParameter,
206            extension::SymbolKind::Other(value) => Self::Other(value),
207        }
208    }
209}
210
211impl From<extension::SlashCommand> for SlashCommand {
212    fn from(value: extension::SlashCommand) -> Self {
213        Self {
214            name: value.name,
215            description: value.description,
216            tooltip_text: value.tooltip_text,
217            requires_argument: value.requires_argument,
218        }
219    }
220}
221
222impl From<SlashCommandOutput> for extension::SlashCommandOutput {
223    fn from(value: SlashCommandOutput) -> Self {
224        Self {
225            text: value.text,
226            sections: value.sections.into_iter().map(Into::into).collect(),
227        }
228    }
229}
230
231impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
232    fn from(value: SlashCommandOutputSection) -> Self {
233        Self {
234            range: value.range.start as usize..value.range.end as usize,
235            label: value.label,
236        }
237    }
238}
239
240impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
241    fn from(value: SlashCommandArgumentCompletion) -> Self {
242        Self {
243            label: value.label,
244            new_text: value.new_text,
245            run_command: value.run_command,
246        }
247    }
248}
249
250impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
251    type Error = anyhow::Error;
252
253    fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
254        let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
255            .context("Failed to parse settings_schema")?;
256
257        Ok(Self {
258            installation_instructions: value.installation_instructions,
259            default_settings: value.default_settings,
260            settings_schema,
261        })
262    }
263}
264
265impl HostKeyValueStore for WasmState {
266    async fn insert(
267        &mut self,
268        kv_store: Resource<ExtensionKeyValueStore>,
269        key: String,
270        value: String,
271    ) -> wasmtime::Result<Result<(), String>> {
272        let kv_store = self.table.get(&kv_store)?;
273        kv_store.insert(key, value).await.to_wasmtime_result()
274    }
275
276    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
277        // We only ever hand out borrows of key-value stores.
278        Ok(())
279    }
280}
281
282impl HostProject for WasmState {
283    async fn worktree_ids(
284        &mut self,
285        project: Resource<ExtensionProject>,
286    ) -> wasmtime::Result<Vec<u64>> {
287        let project = self.table.get(&project)?;
288        Ok(project.worktree_ids())
289    }
290
291    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
292        // We only ever hand out borrows of projects.
293        Ok(())
294    }
295}
296
297impl HostWorktree for WasmState {
298    async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
299        let delegate = self.table.get(&delegate)?;
300        Ok(delegate.id())
301    }
302
303    async fn root_path(
304        &mut self,
305        delegate: Resource<Arc<dyn WorktreeDelegate>>,
306    ) -> wasmtime::Result<String> {
307        let delegate = self.table.get(&delegate)?;
308        Ok(delegate.root_path())
309    }
310
311    async fn read_text_file(
312        &mut self,
313        delegate: Resource<Arc<dyn WorktreeDelegate>>,
314        path: String,
315    ) -> wasmtime::Result<Result<String, String>> {
316        let delegate = self.table.get(&delegate)?;
317        Ok(delegate
318            .read_text_file(path.into())
319            .await
320            .map_err(|error| error.to_string()))
321    }
322
323    async fn shell_env(
324        &mut self,
325        delegate: Resource<Arc<dyn WorktreeDelegate>>,
326    ) -> wasmtime::Result<EnvVars> {
327        let delegate = self.table.get(&delegate)?;
328        Ok(delegate.shell_env().await.into_iter().collect())
329    }
330
331    async fn which(
332        &mut self,
333        delegate: Resource<Arc<dyn WorktreeDelegate>>,
334        binary_name: String,
335    ) -> wasmtime::Result<Option<String>> {
336        let delegate = self.table.get(&delegate)?;
337        Ok(delegate.which(binary_name).await)
338    }
339
340    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
341        // We only ever hand out borrows of worktrees.
342        Ok(())
343    }
344}
345
346impl common::Host for WasmState {}
347
348impl http_client::Host for WasmState {
349    async fn fetch(
350        &mut self,
351        request: http_client::HttpRequest,
352    ) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
353        maybe!(async {
354            let url = &request.url;
355            let request = convert_request(&request)?;
356            let mut response = self.host.http_client.send(request).await?;
357
358            if response.status().is_client_error() || response.status().is_server_error() {
359                bail!("failed to fetch '{url}': status code {}", response.status())
360            }
361            convert_response(&mut response).await
362        })
363        .await
364        .to_wasmtime_result()
365    }
366
367    async fn fetch_stream(
368        &mut self,
369        request: http_client::HttpRequest,
370    ) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
371        let request = convert_request(&request)?;
372        let response = self.host.http_client.send(request);
373        maybe!(async {
374            let response = response.await?;
375            let stream = Arc::new(Mutex::new(response));
376            let resource = self.table.push(stream)?;
377            Ok(resource)
378        })
379        .await
380        .to_wasmtime_result()
381    }
382}
383
384impl http_client::HostHttpResponseStream for WasmState {
385    async fn next_chunk(
386        &mut self,
387        resource: Resource<ExtensionHttpResponseStream>,
388    ) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
389        let stream = self.table.get(&resource)?.clone();
390        maybe!(async move {
391            let mut response = stream.lock().await;
392            let mut buffer = vec![0; 8192]; // 8KB buffer
393            let bytes_read = response.body_mut().read(&mut buffer).await?;
394            if bytes_read == 0 {
395                Ok(None)
396            } else {
397                buffer.truncate(bytes_read);
398                Ok(Some(buffer))
399            }
400        })
401        .await
402        .to_wasmtime_result()
403    }
404
405    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
406        Ok(())
407    }
408}
409
410impl From<http_client::HttpMethod> for ::http_client::Method {
411    fn from(value: http_client::HttpMethod) -> Self {
412        match value {
413            http_client::HttpMethod::Get => Self::GET,
414            http_client::HttpMethod::Post => Self::POST,
415            http_client::HttpMethod::Put => Self::PUT,
416            http_client::HttpMethod::Delete => Self::DELETE,
417            http_client::HttpMethod::Head => Self::HEAD,
418            http_client::HttpMethod::Options => Self::OPTIONS,
419            http_client::HttpMethod::Patch => Self::PATCH,
420        }
421    }
422}
423
424fn convert_request(
425    extension_request: &http_client::HttpRequest,
426) -> Result<::http_client::Request<AsyncBody>, anyhow::Error> {
427    let mut request = ::http_client::Request::builder()
428        .method(::http_client::Method::from(extension_request.method))
429        .uri(&extension_request.url)
430        .follow_redirects(match extension_request.redirect_policy {
431            http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
432            http_client::RedirectPolicy::FollowLimit(limit) => {
433                ::http_client::RedirectPolicy::FollowLimit(limit)
434            }
435            http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
436        });
437    for (key, value) in &extension_request.headers {
438        request = request.header(key, value);
439    }
440    let body = extension_request
441        .body
442        .clone()
443        .map(AsyncBody::from)
444        .unwrap_or_default();
445    request.body(body).map_err(anyhow::Error::from)
446}
447
448async fn convert_response(
449    response: &mut ::http_client::Response<AsyncBody>,
450) -> Result<http_client::HttpResponse, anyhow::Error> {
451    let mut extension_response = http_client::HttpResponse {
452        body: Vec::new(),
453        headers: Vec::new(),
454    };
455
456    for (key, value) in response.headers() {
457        extension_response
458            .headers
459            .push((key.to_string(), value.to_str().unwrap_or("").to_string()));
460    }
461
462    response
463        .body_mut()
464        .read_to_end(&mut extension_response.body)
465        .await?;
466
467    Ok(extension_response)
468}
469
470impl nodejs::Host for WasmState {
471    async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
472        self.host
473            .node_runtime
474            .binary_path()
475            .await
476            .map(|path| path.to_string_lossy().to_string())
477            .to_wasmtime_result()
478    }
479
480    async fn npm_package_latest_version(
481        &mut self,
482        package_name: String,
483    ) -> wasmtime::Result<Result<String, String>> {
484        self.host
485            .node_runtime
486            .npm_package_latest_version(&package_name)
487            .await
488            .to_wasmtime_result()
489    }
490
491    async fn npm_package_installed_version(
492        &mut self,
493        package_name: String,
494    ) -> wasmtime::Result<Result<Option<String>, String>> {
495        self.host
496            .node_runtime
497            .npm_package_installed_version(&self.work_dir(), &package_name)
498            .await
499            .to_wasmtime_result()
500    }
501
502    async fn npm_install_package(
503        &mut self,
504        package_name: String,
505        version: String,
506    ) -> wasmtime::Result<Result<(), String>> {
507        self.host
508            .node_runtime
509            .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
510            .await
511            .to_wasmtime_result()
512    }
513}
514
515#[async_trait]
516impl lsp::Host for WasmState {}
517
518impl From<::http_client::github::GithubRelease> for github::GithubRelease {
519    fn from(value: ::http_client::github::GithubRelease) -> Self {
520        Self {
521            version: value.tag_name,
522            assets: value.assets.into_iter().map(Into::into).collect(),
523        }
524    }
525}
526
527impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
528    fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
529        Self {
530            name: value.name,
531            download_url: value.browser_download_url,
532        }
533    }
534}
535
536impl github::Host for WasmState {
537    async fn latest_github_release(
538        &mut self,
539        repo: String,
540        options: github::GithubReleaseOptions,
541    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
542        maybe!(async {
543            let release = ::http_client::github::latest_github_release(
544                &repo,
545                options.require_assets,
546                options.pre_release,
547                self.host.http_client.clone(),
548            )
549            .await?;
550            Ok(release.into())
551        })
552        .await
553        .to_wasmtime_result()
554    }
555
556    async fn github_release_by_tag_name(
557        &mut self,
558        repo: String,
559        tag: String,
560    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
561        maybe!(async {
562            let release = ::http_client::github::get_release_by_tag_name(
563                &repo,
564                &tag,
565                self.host.http_client.clone(),
566            )
567            .await?;
568            Ok(release.into())
569        })
570        .await
571        .to_wasmtime_result()
572    }
573}
574
575impl platform::Host for WasmState {
576    async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
577        Ok((
578            match env::consts::OS {
579                "macos" => platform::Os::Mac,
580                "linux" => platform::Os::Linux,
581                "windows" => platform::Os::Windows,
582                _ => panic!("unsupported os"),
583            },
584            match env::consts::ARCH {
585                "aarch64" => platform::Architecture::Aarch64,
586                "x86" => platform::Architecture::X86,
587                "x86_64" => platform::Architecture::X8664,
588                _ => panic!("unsupported architecture"),
589            },
590        ))
591    }
592}
593
594impl From<std::process::Output> for process::Output {
595    fn from(output: std::process::Output) -> Self {
596        Self {
597            status: output.status.code(),
598            stdout: output.stdout,
599            stderr: output.stderr,
600        }
601    }
602}
603
604impl process::Host for WasmState {
605    async fn run_command(
606        &mut self,
607        command: process::Command,
608    ) -> wasmtime::Result<Result<process::Output, String>> {
609        maybe!(async {
610            self.manifest.allow_exec(&command.command, &command.args)?;
611
612            let output = util::command::new_smol_command(command.command.as_str())
613                .args(&command.args)
614                .envs(command.env)
615                .output()
616                .await?;
617
618            Ok(output.into())
619        })
620        .await
621        .to_wasmtime_result()
622    }
623}
624
625#[async_trait]
626impl slash_command::Host for WasmState {}
627
628#[async_trait]
629impl context_server::Host for WasmState {}
630
631impl ExtensionImports for WasmState {
632    async fn get_settings(
633        &mut self,
634        location: Option<self::SettingsLocation>,
635        category: String,
636        key: Option<String>,
637    ) -> wasmtime::Result<Result<String, String>> {
638        self.on_main_thread(|cx| {
639            async move {
640                let location = location
641                    .as_ref()
642                    .map(|location| ::settings::SettingsLocation {
643                        worktree_id: WorktreeId::from_proto(location.worktree_id),
644                        path: Path::new(&location.path),
645                    });
646
647                cx.update(|cx| match category.as_str() {
648                    "language" => {
649                        let key = key.map(|k| LanguageName::new(&k));
650                        let settings = AllLanguageSettings::get(location, cx).language(
651                            location,
652                            key.as_ref(),
653                            cx,
654                        );
655                        Ok(serde_json::to_string(&settings::LanguageSettings {
656                            tab_size: settings.tab_size,
657                        })?)
658                    }
659                    "lsp" => {
660                        let settings = key
661                            .and_then(|key| {
662                                ProjectSettings::get(location, cx)
663                                    .lsp
664                                    .get(&::lsp::LanguageServerName::from_proto(key))
665                            })
666                            .cloned()
667                            .unwrap_or_default();
668                        Ok(serde_json::to_string(&settings::LspSettings {
669                            binary: settings.binary.map(|binary| settings::CommandSettings {
670                                path: binary.path,
671                                arguments: binary.arguments,
672                                env: binary.env,
673                            }),
674                            settings: settings.settings,
675                            initialization_options: settings.initialization_options,
676                        })?)
677                    }
678                    "context_servers" => {
679                        let settings = key
680                            .and_then(|key| {
681                                ContextServerSettings::get(location, cx)
682                                    .context_servers
683                                    .get(key.as_str())
684                            })
685                            .cloned()
686                            .unwrap_or_default();
687                        Ok(serde_json::to_string(&settings::ContextServerSettings {
688                            command: settings.command.map(|command| settings::CommandSettings {
689                                path: Some(command.path),
690                                arguments: Some(command.args),
691                                env: command.env.map(|env| env.into_iter().collect()),
692                            }),
693                            settings: settings.settings,
694                        })?)
695                    }
696                    _ => {
697                        bail!("Unknown settings category: {}", category);
698                    }
699                })
700            }
701            .boxed_local()
702        })
703        .await?
704        .to_wasmtime_result()
705    }
706
707    async fn set_language_server_installation_status(
708        &mut self,
709        server_name: String,
710        status: LanguageServerInstallationStatus,
711    ) -> wasmtime::Result<()> {
712        let status = match status {
713            LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
714            LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
715            LanguageServerInstallationStatus::None => BinaryStatus::None,
716            LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
717        };
718
719        self.host
720            .proxy
721            .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
722
723        Ok(())
724    }
725
726    async fn download_file(
727        &mut self,
728        url: String,
729        path: String,
730        file_type: DownloadedFileType,
731    ) -> wasmtime::Result<Result<(), String>> {
732        maybe!(async {
733            let path = PathBuf::from(path);
734            let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
735
736            self.host.fs.create_dir(&extension_work_dir).await?;
737
738            let destination_path = self
739                .host
740                .writeable_path_from_extension(&self.manifest.id, &path)?;
741
742            let mut response = self
743                .host
744                .http_client
745                .get(&url, Default::default(), true)
746                .await
747                .map_err(|err| anyhow!("error downloading release: {}", err))?;
748
749            if !response.status().is_success() {
750                Err(anyhow!(
751                    "download failed with status {}",
752                    response.status().to_string()
753                ))?;
754            }
755            let body = BufReader::new(response.body_mut());
756
757            match file_type {
758                DownloadedFileType::Uncompressed => {
759                    futures::pin_mut!(body);
760                    self.host
761                        .fs
762                        .create_file_with(&destination_path, body)
763                        .await?;
764                }
765                DownloadedFileType::Gzip => {
766                    let body = GzipDecoder::new(body);
767                    futures::pin_mut!(body);
768                    self.host
769                        .fs
770                        .create_file_with(&destination_path, body)
771                        .await?;
772                }
773                DownloadedFileType::GzipTar => {
774                    let body = GzipDecoder::new(body);
775                    futures::pin_mut!(body);
776                    self.host
777                        .fs
778                        .extract_tar_file(&destination_path, Archive::new(body))
779                        .await?;
780                }
781                DownloadedFileType::Zip => {
782                    futures::pin_mut!(body);
783                    node_runtime::extract_zip(&destination_path, body)
784                        .await
785                        .with_context(|| format!("failed to unzip {} archive", path.display()))?;
786                }
787            }
788
789            Ok(())
790        })
791        .await
792        .to_wasmtime_result()
793    }
794
795    async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
796        #[allow(unused)]
797        let path = self
798            .host
799            .writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
800
801        #[cfg(unix)]
802        {
803            use std::fs::{self, Permissions};
804            use std::os::unix::fs::PermissionsExt;
805
806            return fs::set_permissions(&path, Permissions::from_mode(0o755))
807                .map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
808                .to_wasmtime_result();
809        }
810
811        #[cfg(not(unix))]
812        Ok(Ok(()))
813    }
814}