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}