harn_cli/package/lockfile/
install.rs1use crate::package::*;
5
6pub(crate) fn install_packages_impl(
7 frozen: bool,
8 refetch: Option<&str>,
9 offline: bool,
10) -> Result<usize, PackageError> {
11 install_packages_in(
12 &PackageWorkspace::from_current_dir()?,
13 frozen,
14 refetch,
15 offline,
16 )
17}
18
19pub(crate) fn install_packages_in_locked(
20 workspace: &PackageWorkspace,
21 frozen: bool,
22 refetch: Option<&str>,
23 offline: bool,
24) -> Result<usize, PackageError> {
25 let ctx = workspace.load_manifest_context()?;
26 let existing = LockFile::load(&ctx.lock_path())?;
27 if ctx.manifest.dependencies.is_empty() {
28 let empty = LockFile::default();
29 if frozen || offline {
30 if existing
34 .as_ref()
35 .is_some_and(|lock| !lock.packages.is_empty())
36 {
37 return Err(format!("{} would need to change", ctx.lock_path().display()).into());
38 }
39 } else {
40 empty.save(&ctx.lock_path())?;
41 }
42 return materialize_dependencies_from_lock(workspace, &ctx, &empty, refetch, offline);
43 }
44
45 if (frozen || offline) && existing.is_none() {
46 return Err(format!("{} is missing", ctx.lock_path().display()).into());
47 }
48
49 let desired = build_lockfile(
50 workspace,
51 &ctx,
52 existing.as_ref(),
53 None,
54 false,
55 !frozen && !offline,
56 offline,
57 )?;
58 if frozen || offline {
59 if !existing
60 .as_ref()
61 .is_some_and(|lock| lock.same_resolution(&desired))
62 {
63 return Err(format!("{} would need to change", ctx.lock_path().display()).into());
64 }
65 } else {
66 desired.save(&ctx.lock_path())?;
67 }
68 materialize_dependencies_from_lock(workspace, &ctx, &desired, refetch, offline)
69}
70
71pub fn install_packages(frozen: bool, refetch: Option<&str>, offline: bool, json: bool) {
72 match install_packages_impl(frozen, refetch, offline) {
73 Ok(installed) if json => {
74 print_install_summary_json("install", installed, frozen, offline);
75 }
76 Ok(0) => println!("No dependencies to install."),
77 Ok(installed) => {
78 println!("Installed {installed} package(s) in a new immutable generation.");
79 }
80 Err(error) if json => {
81 print_install_error_json("install", &error);
82 process::exit(1);
83 }
84 Err(error) => {
85 eprintln!("error: {error}");
86 process::exit(1);
87 }
88 }
89}
90
91fn print_install_summary_json(action: &str, installed: usize, frozen: bool, offline: bool) {
92 let body = serde_json::json!({
93 "action": action,
94 "ok": true,
95 "installed": installed,
96 "frozen": frozen,
97 "offline": offline,
98 "lock_file": LOCK_FILE,
99 "package_pointer": ".harn/package-current.toml",
100 });
101 println!(
102 "{}",
103 serde_json::to_string_pretty(&body).unwrap_or_default()
104 );
105}
106
107fn print_install_error_json(action: &str, error: &PackageError) {
108 let body = serde_json::json!({
109 "action": action,
110 "ok": false,
111 "error": error.to_string(),
112 });
113 println!(
114 "{}",
115 serde_json::to_string_pretty(&body).unwrap_or_default()
116 );
117}
118pub fn lock_packages() {
119 let result = (|| -> Result<usize, PackageError> {
120 let workspace = PackageWorkspace::from_current_dir()?;
121 let _mutation_lock = acquire_package_mutation_lock(&workspace)?;
122 let ctx = workspace.load_manifest_context()?;
123 let existing = LockFile::load(&ctx.lock_path())?;
124 let lock = build_lockfile(&workspace, &ctx, existing.as_ref(), None, true, true, false)?;
125 lock.save(&ctx.lock_path())?;
126 Ok(lock.packages.len())
127 })();
128
129 match result {
130 Ok(count) => println!("Wrote {LOCK_FILE} with {count} package(s)."),
131 Err(error) => {
132 eprintln!("error: {error}");
133 process::exit(1);
134 }
135 }
136}
137pub fn update_packages(alias: Option<&str>, all: bool, json: bool) {
138 let result = PackageWorkspace::from_current_dir()
139 .and_then(|workspace| update_packages_in(&workspace, alias, all));
140 print_update_packages_result(result, json);
141}
142
143pub(crate) fn update_packages_in(
144 workspace: &PackageWorkspace,
145 alias: Option<&str>,
146 all: bool,
147) -> Result<usize, PackageError> {
148 let _mutation_lock = acquire_package_mutation_lock(workspace)?;
149 if !all && alias.is_none() {
150 return Err("specify a dependency alias or pass --all"
151 .to_string()
152 .into());
153 }
154
155 let ctx = workspace.load_manifest_context()?;
156 if let Some(alias) = alias {
157 validate_package_alias(alias)?;
158 if !ctx.manifest.dependencies.contains_key(alias) {
159 return Err(format!("{alias} is not present in [dependencies]").into());
160 }
161 }
162 let existing = LockFile::load(&ctx.lock_path())?;
163 let lock = build_lockfile(workspace, &ctx, existing.as_ref(), alias, all, true, false)?;
164 lock.save(&ctx.lock_path())?;
165 materialize_dependencies_from_lock(workspace, &ctx, &lock, None, false)
166}
167
168fn print_update_packages_result(result: Result<usize, PackageError>, json: bool) {
169 match result {
170 Ok(installed) if json => print_install_summary_json("update", installed, false, false),
171 Ok(installed) => println!("Updated {installed} package(s)."),
172 Err(error) if json => {
173 print_install_error_json("update", &error);
174 process::exit(1);
175 }
176 Err(error) => {
177 eprintln!("error: {error}");
178 process::exit(1);
179 }
180 }
181}
182pub fn remove_package(alias: &str) {
183 let result = PackageWorkspace::from_current_dir()
184 .and_then(|workspace| remove_package_in(&workspace, alias));
185 print_remove_package_result(alias, result);
186}
187
188pub(crate) fn remove_package_in(
189 workspace: &PackageWorkspace,
190 alias: &str,
191) -> Result<bool, PackageError> {
192 let _mutation_lock = acquire_package_mutation_lock(workspace)?;
193 validate_package_alias(alias)?;
194 let ctx = workspace.load_manifest_context()?;
195 let removed = remove_dependency_from_manifest(&ctx.manifest_path(), alias)?;
196 if !removed {
197 return Ok(false);
198 }
199 let mut lock = LockFile::load(&ctx.lock_path())?.unwrap_or_default();
200 lock.remove(alias);
201 lock.save(&ctx.lock_path())?;
202 materialize_dependencies_from_lock(workspace, &ctx, &lock, None, false)?;
203 Ok(true)
204}
205
206fn print_remove_package_result(alias: &str, result: Result<bool, PackageError>) {
207 match result {
208 Ok(true) => println!("Removed {alias} from {MANIFEST} and {LOCK_FILE}."),
209 Ok(false) => {
210 eprintln!("error: {alias} is not present in [dependencies]");
211 process::exit(1);
212 }
213 Err(error) => {
214 eprintln!("error: {error}");
215 process::exit(1);
216 }
217 }
218}