Skip to main content

acts_package_shell/
package.rs

1use 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(&params.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}