1use crate::wasm_host::wit::since_v0_6_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, 6, 0);
27pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
28
29wasmtime::component::bindgen!({
30 async: true,
31 trappable_imports: true,
32 path: "../extension_api/wit/since_v0.6.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}