1use std::path::{Path, PathBuf};
2
3use crate::{args::PackageOrRockspec, build, workspace::exists_matching_workspace_member};
4use clap::Args;
5use itertools::Itertools;
6use lux_lib::{
7 build::{Build, BuildBehaviour},
8 config::Config,
9 lua_installation::LuaInstallation,
10 lua_rockspec::RemoteLuaRockspec,
11 lua_version::LuaVersion,
12 operations::{self, Install, PackageInstallSpec},
13 package::PackageName,
14 rockspec::Rockspec as _,
15 tree::{self, InstallTree},
16 workspace::Workspace,
17};
18use miette::{miette, IntoDiagnostic, Result};
19use path_slash::PathBufExt;
20use tempfile::tempdir;
21
22#[derive(Args)]
23pub struct Pack {
24 #[clap(value_parser)]
40 package_or_rockspec: Option<PackageOrRockspec>,
41}
42
43async fn pack_workspace(
44 member: Option<&PackageName>,
45 dest_dir: &Path,
46 config: &Config,
47) -> Result<Vec<PathBuf>> {
48 let workspace = Workspace::current_or_err()?;
49
50 let packages = match member {
53 Some(package_name) => {
55 let project = workspace.select_member(package_name)?;
56 project
57 .toml()
58 .into_remote(None)?
59 .to_lua_remote_rockspec_string()?;
60
61 let mut build = build::Build::default();
62 build.package = Some(package_name.clone());
63 build::build(build, config.clone())
64 }
65 None => {
67 for project in workspace.members() {
68 project
69 .toml()
70 .into_remote(None)?
71 .to_lua_remote_rockspec_string()?;
72 }
73 build::build(build::Build::default(), config.clone())
74 }
75 }
76 .await?;
77
78 if packages.is_empty() {
79 return Err(miette!("build did not produce a package"));
80 }
81
82 let mut rock_paths = Vec::new();
83 for package in packages {
84 let tree = workspace.tree(config)?;
85 let rock_path = operations::Pack::new(dest_dir.to_path_buf(), tree, package)
86 .pack()
87 .await?;
88 rock_paths.push(rock_path);
89 }
90
91 Ok(rock_paths)
92}
93
94pub async fn pack(args: Pack, config: Config) -> Result<()> {
95 let lua_version = LuaVersion::from(&config)?.clone();
96 let dest_dir = std::env::current_dir().into_diagnostic()?;
97 let rock_paths: Vec<PathBuf> = match args.package_or_rockspec {
98 Some(PackageOrRockspec::Package(package_req))
99 if exists_matching_workspace_member(&package_req)? =>
100 {
101 pack_workspace(Some(package_req.name()), &dest_dir, &config).await
102 }
103 Some(PackageOrRockspec::Package(package_req)) => {
104 let user_tree = config.user_tree(lua_version.clone())?;
105 match user_tree.match_rocks(&package_req)? {
106 lux_lib::tree::RockMatches::NotFound(_) => {
107 let temp_dir = tempdir().into_diagnostic()?;
108 let temp_config = config.with_tree(temp_dir.path().to_path_buf());
109 let tree = temp_config.user_tree(lua_version.clone())?;
110 let packages = Install::new(&temp_config)
111 .package(
112 PackageInstallSpec::new(package_req, tree::EntryType::Entrypoint)
113 .build_behaviour(BuildBehaviour::Force)
114 .build(),
115 )
116 .tree(tree.clone())
117 .install()
118 .await?;
119 let package = packages
120 .first()
121 .ok_or_else(|| miette!("no packages installed"))?;
122 let rock_path = operations::Pack::new(dest_dir, tree, package.clone())
123 .pack()
124 .await?;
125 Ok(vec![rock_path])
126 }
127 lux_lib::tree::RockMatches::Single(local_package_id) => {
128 let lockfile = user_tree.lockfile()?;
129 let package = lockfile.get(&local_package_id).ok_or_else(|| {
130 miette!("package is installed, but was not found in the lockfile")
131 })?;
132 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
133 .pack()
134 .await?;
135 Ok(vec![rock_path])
136 }
137 lux_lib::tree::RockMatches::Many(vec) => {
138 let local_package_id = vec.first();
139 let lockfile = user_tree.lockfile()?;
140 let package = lockfile.get(local_package_id).ok_or_else(|| {
141 miette!(
142 "multiple package installations found, but not found in the lockfile"
143 )
144 })?;
145 let rock_path = operations::Pack::new(dest_dir, user_tree, package.clone())
146 .pack()
147 .await?;
148 Ok(vec![rock_path])
149 }
150 }
151 }
152 Some(PackageOrRockspec::RockSpec(rockspec_path)) => {
153 let content = tokio::fs::read_to_string(&rockspec_path)
154 .await
155 .into_diagnostic()?;
156 let rockspec = match rockspec_path
157 .extension()
158 .map(|ext| ext.to_string_lossy().to_string())
159 .unwrap_or("".into())
160 .as_str()
161 {
162 "rockspec" => Ok(RemoteLuaRockspec::new(&content)?),
163 _ => Err(miette!(
164 "expected a path to a .rockspec or a package requirement."
165 )),
166 }?;
167 let temp_dir = tempdir().into_diagnostic()?;
168 let config = config.with_tree(temp_dir.path().to_path_buf());
169 let lua = LuaInstallation::new(&lua_version, &config).await?;
170 let tree = config.user_tree(lua_version)?;
171 let package = Build::new()
172 .rockspec(&rockspec)
173 .lua(&lua)
174 .tree(&tree)
175 .entry_type(tree::EntryType::Entrypoint)
176 .config(&config)
177 .build()
178 .await?;
179 let rock_path = operations::Pack::new(dest_dir, tree, package)
180 .pack()
181 .await?;
182 Ok(vec![rock_path])
183 }
184 None => pack_workspace(None, &dest_dir, &config).await,
185 }?;
186
187 if rock_paths.len() > 1 {
188 let rock_paths = rock_paths
189 .iter()
190 .map(|path| path.to_slash_lossy().to_string())
191 .join("\n");
192 print!("packed rocks created at\n{}", rock_paths)
193 } else {
194 rock_paths
195 .first()
196 .iter()
197 .for_each(|path| print!("packed rock created at {}", path.display()));
198 }
199 Ok(())
200}