lux_cli/
pack.rs

1use std::{path::PathBuf, str::FromStr};
2
3use clap::Args;
4use eyre::{eyre, Result};
5use lux_lib::{
6    build::{Build, BuildBehaviour},
7    config::{Config, LuaVersion},
8    lua_rockspec::RemoteLuaRockspec,
9    operations::{self, Install, PackageInstallSpec},
10    package::PackageReq,
11    progress::MultiProgress,
12    project::Project,
13    tree,
14};
15use tempdir::TempDir;
16
17#[derive(Debug, Clone)]
18pub enum PackageOrRockspec {
19    Package(PackageReq),
20    RockSpec(PathBuf),
21}
22
23impl FromStr for PackageOrRockspec {
24    type Err = eyre::Error;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        let path = PathBuf::from(s);
28        if path.is_file() {
29            Ok(Self::RockSpec(path))
30        } else {
31            let pkg = PackageReq::from_str(s).map_err(|err| {
32                eyre!(
33                    "No file {0} found and cannot parse package query: {1}",
34                    s,
35                    err
36                )
37            })?;
38            Ok(Self::Package(pkg))
39        }
40    }
41}
42
43#[derive(Args)]
44pub struct Pack {
45    /// Path to a RockSpec or a package query for a package to pack.
46    /// Prioritises installed rocks and will install a rock to a temporary
47    /// directory if none is found.
48    /// In case of multiple matches, the latest version will be packed.
49    /// Examples: "pkg", "pkg@1.0.0", "pkg>=1.0.0"
50    ///
51    /// If not set, rocks will pack the current project's lux.toml.
52    #[clap(value_parser)]
53    package_or_rockspec: Option<PackageOrRockspec>,
54}
55
56pub async fn pack(args: Pack, config: Config) -> Result<()> {
57    let lua_version = LuaVersion::from(&config)?.clone();
58    let dest_dir = std::env::current_dir()?;
59    let progress = MultiProgress::new_arc();
60    let package_or_rockspec = match args.package_or_rockspec {
61        Some(package_or_rockspec) => package_or_rockspec,
62        None => {
63            let project = Project::current_or_err()?;
64            PackageOrRockspec::RockSpec(project.toml_path())
65        }
66    };
67    let result: Result<PathBuf> = match package_or_rockspec {
68        PackageOrRockspec::Package(package_req) => {
69            let user_tree = config.user_tree(lua_version.clone())?;
70            match user_tree.match_rocks(&package_req)? {
71                lux_lib::tree::RockMatches::NotFound(_) => {
72                    let temp_dir = TempDir::new("lux-pack")?.into_path();
73                    let temp_config = config.with_tree(temp_dir);
74                    let tree = temp_config.user_tree(lua_version.clone())?;
75                    let packages = Install::new(&temp_config)
76                        .package(
77                            PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
78                                .build_behaviour(BuildBehaviour::Force)
79                                .build(),
80                        )
81                        .tree(tree.clone())
82                        .progress(progress)
83                        .install()
84                        .await?;
85                    let package = packages.first().unwrap();
86                    let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
87                        .pack()
88                        .await?;
89                    Ok(rock_path)
90                }
91                lux_lib::tree::RockMatches::Single(local_package_id) => {
92                    let lockfile = user_tree.lockfile()?;
93                    let package = lockfile.get(&local_package_id).unwrap();
94                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
95                        .pack()
96                        .await?;
97                    Ok(rock_path)
98                }
99                lux_lib::tree::RockMatches::Many(vec) => {
100                    let local_package_id = vec.first().unwrap();
101                    let lockfile = user_tree.lockfile()?;
102                    let package = lockfile.get(local_package_id).unwrap();
103                    let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
104                        .pack()
105                        .await?;
106                    Ok(rock_path)
107                }
108            }
109        }
110        PackageOrRockspec::RockSpec(rockspec_path) => {
111            let content = std::fs::read_to_string(&rockspec_path)?;
112            let rockspec = match rockspec_path
113                .extension()
114                .map(|ext| ext.to_string_lossy().to_string())
115                .unwrap_or("".into())
116                .as_str()
117            {
118                ".rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
119                _ => Err(eyre!(
120                    "expected a path to a .rockspec or a package requirement."
121                )),
122            }?;
123            let temp_dir = TempDir::new("lux-pack")?.into_path();
124            let bar = progress.map(|p| p.new_bar());
125            let config = config.with_tree(temp_dir);
126            let tree = config.user_tree(lua_version)?;
127            let package = Build::new(&rockspec, &tree, tree::EntryType::Entrypoint, &config, &bar)
128                .build()
129                .await?;
130            let rock_path = operations::Pack::new(dest_dir, tree, package)
131                .pack()
132                .await?;
133            Ok(rock_path)
134        }
135    };
136    print!("packed rock created at {}", result?.display());
137    Ok(())
138}