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