1use std::{path::PathBuf, str::FromStr};
2
3use crate::build;
4use clap::Args;
5use eyre::{eyre, OptionExt, Result};
6use lux_lib::{
7 build::{Build, BuildBehaviour},
8 config::{Config, LuaVersion},
9 lua_installation::LuaInstallation,
10 lua_rockspec::RemoteLuaRockspec,
11 operations::{self, Install, PackageInstallSpec},
12 package::PackageReq,
13 progress::MultiProgress,
14 project::Project,
15 rockspec::Rockspec as _,
16 tree,
17};
18use tempfile::tempdir;
19
20#[derive(Debug, Clone)]
21pub enum PackageOrRockspec {
22 Package(PackageReq),
23 RockSpec(PathBuf),
24}
25
26impl FromStr for PackageOrRockspec {
27 type Err = eyre::Error;
28
29 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 let path = PathBuf::from(s);
31 if path.is_file() {
32 Ok(Self::RockSpec(path))
33 } else {
34 let pkg = PackageReq::from_str(s).map_err(|err| {
35 eyre!(
36 "No file {0} found and cannot parse package query: {1}",
37 s,
38 err
39 )
40 })?;
41 Ok(Self::Package(pkg))
42 }
43 }
44}
45
46#[derive(Args)]
47pub struct Pack {
48 #[clap(value_parser)]
63 package_or_rockspec: Option<PackageOrRockspec>,
64}
65
66pub async fn pack(args: Pack, config: Config) -> Result<()> {
67 let lua_version = LuaVersion::from(&config)?.clone();
68 let dest_dir = std::env::current_dir()?;
69 let progress = MultiProgress::new_arc(&config);
70 let result: Result<PathBuf> = match args.package_or_rockspec {
71 Some(PackageOrRockspec::Package(package_req)) => {
72 let user_tree = config.user_tree(lua_version.clone())?;
73 match user_tree.match_rocks(&package_req)? {
74 lux_lib::tree::RockMatches::NotFound(_) => {
75 let temp_dir = tempdir()?;
76 let temp_config = config.with_tree(temp_dir.path().to_path_buf());
77 let tree = temp_config.user_tree(lua_version.clone())?;
78 let packages = Install::new(&temp_config)
79 .package(
80 PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
81 .build_behaviour(BuildBehaviour::Force)
82 .build(),
83 )
84 .tree(tree.clone())
85 .progress(progress)
86 .install()
87 .await?;
88 let package = packages.first().ok_or_eyre("no packages installed")?;
89 let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
90 .pack()
91 .await?;
92 Ok(rock_path)
93 }
94 lux_lib::tree::RockMatches::Single(local_package_id) => {
95 let lockfile = user_tree.lockfile()?;
96 let package = lockfile
97 .get(&local_package_id)
98 .ok_or_eyre("package is installed, but was not found in the lockfile")?;
99 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
100 .pack()
101 .await?;
102 Ok(rock_path)
103 }
104 lux_lib::tree::RockMatches::Many(vec) => {
105 let local_package_id = vec.first();
106 let lockfile = user_tree.lockfile()?;
107 let package = lockfile.get(local_package_id).ok_or_eyre(
108 "multiple package installations found, but not found in the lockfile",
109 )?;
110 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
111 .pack()
112 .await?;
113 Ok(rock_path)
114 }
115 }
116 }
117 Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
118 let content = tokio::fs::read_to_string(&rockspec_path).await?;
119 let rockspec = match rockspec_path
120 .extension()
121 .map(|ext| ext.to_string_lossy().to_string())
122 .unwrap_or("".into())
123 .as_str()
124 {
125 "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
126 _ => Err(eyre!(
127 "expected a path to a .rockspec or a package requirement."
128 )),
129 }?;
130 let temp_dir = tempdir()?;
131 let bar = progress.map(|p| p.new_bar());
132 let config = config.with_tree(temp_dir.path().to_path_buf());
133 let lua = LuaInstallation::new(
134 &lua_version,
135 &config,
136 &progress.map(|progress| progress.new_bar()),
137 )
138 .await?;
139 let tree = config.user_tree(lua_version)?;
140 let package = Build::new()
141 .rockspec(&rockspec)
142 .lua(&lua)
143 .tree(&tree)
144 .entry_type(tree::EntryType::Entrypoint)
145 .config(&config)
146 .progress(&bar)
147 .build()
148 .await?;
149 let rock_path = operations::Pack::new(dest_dir, tree, package)
150 .pack()
151 .await?;
152 Ok(rock_path)
153 }
154 None => {
155 let project = Project::current_or_err()?;
156 project
159 .toml()
160 .into_remote(None)?
161 .to_lua_remote_rockspec_string()?;
162 let package = build::build(build::Build::default(), config.clone())
163 .await?
164 .ok_or_eyre("build did not produce a package")?;
165 let tree = project.tree(&config)?;
166 let rock_path = operations::Pack::new(dest_dir, tree, package)
167 .pack()
168 .await?;
169 Ok(rock_path)
170 }
171 };
172 print!("packed rock created at {}", result?.display());
173 Ok(())
174}