acts_package_shell/
package.rs1use acts::{
2 ActError, ActPackage, ActPackageCatalog, ActPackageDefinition, ActRunAs, Result, Vars,
3 include_json,
4};
5use serde::{Deserialize, Serialize};
6use serde_json::{Value as JsonValue, json};
7use std::process::Command;
8use strum::AsRefStr;
9
10const DATA_KEY: &str = "data";
11
12#[derive(Debug, Clone, Deserialize, Serialize, AsRefStr)]
13pub enum Shell {
14 #[serde(rename(deserialize = "sh"))]
15 #[strum(serialize = "sh")]
16 Sh,
17 #[allow(clippy::enum_variant_names)]
18 #[serde(rename(deserialize = "nu"))]
19 #[strum(serialize = "nu")]
20 NuShell,
21 #[serde(rename(deserialize = "bash"))]
22 #[strum(serialize = "bash")]
23 Bash,
24 #[allow(clippy::enum_variant_names)]
25 #[serde(rename(deserialize = "powershell"))]
26 #[strum(serialize = "powershell")]
27 PowerShell,
28}
29
30#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
31pub enum ContentType {
32 #[serde(rename(deserialize = "text"))]
33 Text,
34 #[serde(rename(deserialize = "json"))]
35 Json,
36}
37
38#[derive(Debug, Clone)]
39pub struct ShellPackage;
40
41#[derive(Debug, Clone, Deserialize, Serialize)]
42pub struct ShellPackageParams {
43 shell: Option<Shell>,
44 script: String,
45 #[serde(rename(deserialize = "content-type"))]
46 content_type: Option<ContentType>,
47}
48
49#[async_trait::async_trait]
50impl ActPackage for ShellPackage {
51 fn definition() -> ActPackageDefinition {
52 ActPackageDefinition {
53 id: "acts.app.shell",
54 name: "Shell",
55 desc: "do shell script with nushell, bash or powershell",
56 version: "0.1.0",
57 icon: r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-chevron-right-icon lucide-square-chevron-right"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m10 8 4 4-4 4"/></svg>"#,
58 doc: "",
59 schema: include_json!("./schema.json"),
60 options: Some(json!({
61 "ui:order": ["shell", "script", "content-type"],
62 "script": {
63 "ui:widget": "textarea",
64 },
65 })),
66 run_as: ActRunAs::Func,
67 resources: vec![],
68 catalog: ActPackageCatalog::App,
69 }
70 }
71 fn new(_: &acts::Config) -> Result<Self>
72 where
73 Self: Sized,
74 {
75 Ok(Self)
76 }
77
78 async fn execute(
79 &self,
80 _ctx: &acts::Context,
81 params: &serde_json::Value,
82 ) -> Result<Option<Vars>> {
83 let mut ret = Vars::new();
84
85 let params = serde_json::from_value::<ShellPackageParams>(params.clone()).map_err(|e| {
86 ActError::Package(format!(
87 "invalid ActPackage({}) params: {}",
88 Self::definition().id,
89 e
90 ))
91 })?;
92
93 let shell = params.shell.as_ref().unwrap_or(&Shell::Sh);
94 let output = Command::new(shell.as_ref())
95 .arg("-c")
96 .arg(¶ms.script)
97 .output()
98 .map_err(|err| ActError::Package(format!("{err}")))?;
99
100 if !output.status.success() {
101 let err = String::from_utf8(output.stderr)?;
102 return Err(ActError::Package(err));
103 }
104 let data = String::from_utf8(output.stdout)?;
105 let content_type = params.content_type.as_ref().unwrap_or(&ContentType::Text);
106 match content_type {
107 ContentType::Text => ret.set(DATA_KEY, data),
108 ContentType::Json => ret.set(
109 DATA_KEY,
110 serde_json::from_str::<JsonValue>(&data).map_err(|err| {
111 ActError::Package(format!("failed to convert data to json: {err}"))
112 })?,
113 ),
114 }
115
116 Ok(Some(ret))
117 }
118}