since_v0_2_0.rs

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