Skip to content

Commit 51b8912

Browse files
authored
refactor(pm): refact rebuild.rs (#2232)
1 parent ba18c04 commit 51b8912

File tree

8 files changed

+722
-284
lines changed

8 files changed

+722
-284
lines changed

crates/pm/src/cmd/rebuild.rs

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
1-
use crate::service::package::PackageService;
2-
use anyhow::{Context, Result};
1+
use crate::helper::lock::ensure_package_lock;
2+
use crate::service::rebuild::RebuildService;
3+
use anyhow::Result;
34
use std::path::Path;
45

56
pub async fn rebuild(root_path: &Path) -> Result<()> {
6-
let packages = PackageService::collect_packages(root_path)
7-
.await
8-
.map_err(|e| anyhow::anyhow!("Failed to collect packages: {}", e))?;
9-
10-
let execution_queues = PackageService::create_execution_queues(packages)
11-
.context("Failed to create execution queues")?;
12-
PackageService::execute_queues(execution_queues)
13-
.await
14-
.map_err(|e| anyhow::anyhow!("Failed to execute queues: {}", e))?;
15-
16-
// Handle project's own rebuild logic
17-
// Since package-lock.json doesn't have root node information for "", need to manually add
18-
// We align with npm cli's logic: after dependency reify completes, manually execute project's own scripts
19-
// const scripts = [
20-
// 'preinstall',
21-
// 'install',
22-
// 'postinstall',
23-
// 'prepublish', // XXX(npm9) should we remove this finally??
24-
// 'preprepare',
25-
// 'prepare',
26-
// 'postprepare',
27-
// ]
28-
29-
PackageService::process_project_hooks(root_path)
30-
.await
31-
.map_err(|e| anyhow::anyhow!("Failed to process project hooks: {}", e))?;
32-
33-
Ok(())
7+
let package_lock = ensure_package_lock(root_path).await?;
8+
RebuildService::rebuild(&package_lock, root_path, false).await
349
}

crates/pm/src/helper/lock.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::sync::Arc;
88
use crate::helper::workspace::find_workspaces;
99
use crate::model::node::{EdgeType, Node};
1010
use crate::model::override_rule::Overrides;
11+
use crate::model::package_lock::LockPackage;
1112
use crate::util::config::get_legacy_peer_deps;
1213
use crate::util::json::{load_package_json_from_path, load_package_lock_json_from_path};
1314
use crate::util::logger::{log_verbose, log_warning};
@@ -20,22 +21,14 @@ use crate::{cmd::deps::build_deps, util::logger::log_info};
2021

2122
use super::workspace::find_workspace_path;
2223

24+
// Use the model's LockPackage but create a simplified PackageLock for helper functions
2325
#[derive(Deserialize, Serialize, Clone)]
2426
pub struct PackageLock {
25-
pub packages: HashMap<String, Package>,
27+
pub packages: HashMap<String, LockPackage>,
2628
}
2729

28-
#[derive(Deserialize, Serialize, Debug, Clone)]
29-
#[serde(rename_all = "snake_case")]
30-
pub struct Package {
31-
pub name: Option<String>,
32-
pub version: Option<String>,
33-
pub resolved: Option<String>,
34-
pub link: Option<bool>,
35-
pub cpu: Option<Value>,
36-
pub os: Option<Value>,
37-
pub has_install_script: Option<bool>,
38-
}
30+
// Type alias for backward compatibility
31+
pub type Package = LockPackage;
3932

4033
pub fn group_by_depth(
4134
packages: &HashMap<String, Package>,

crates/pm/src/model/package_lock.rs

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use std::fs;
55
use std::path::Path;
66

77
use crate::{
8+
helper::lock::path_to_pkg_name,
89
service::dependency_graph::{DependencyGraphService, DependencyType, PackageNode},
910
util::logger::log_verbose,
1011
};
1112

1213
/// Represents package information in package-lock.json
13-
#[derive(Debug, Clone, Serialize, Deserialize)]
14+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1415
pub struct LockPackage {
1516
#[serde(skip_serializing_if = "Option::is_none")]
1617
pub name: Option<String>,
@@ -51,6 +52,13 @@ pub struct LockPackage {
5152
pub has_install_script: Option<bool>,
5253
#[serde(skip_serializing_if = "Option::is_none")]
5354
pub workspaces: Option<Vec<String>>,
55+
// Additional fields from helper/lock.rs Package
56+
#[serde(skip_serializing_if = "Option::is_none")]
57+
pub link: Option<bool>,
58+
#[serde(skip_serializing_if = "Option::is_none")]
59+
pub cpu: Option<serde_json::Value>,
60+
#[serde(skip_serializing_if = "Option::is_none")]
61+
pub os: Option<serde_json::Value>,
5462
}
5563

5664
impl LockPackage {
@@ -61,8 +69,10 @@ impl LockPackage {
6169
} else if path.is_empty() {
6270
"root".to_string()
6371
} else {
64-
// Extract package name from path
65-
path.split('/').next_back().unwrap_or("unknown").to_string()
72+
// Use existing path_to_pkg_name helper function
73+
path_to_pkg_name(path)
74+
.map(|s| s.to_string())
75+
.unwrap_or_else(|| "unknown".to_string())
6676
}
6777
}
6878

@@ -73,6 +83,23 @@ impl LockPackage {
7383
.unwrap_or_else(|| "unknown".to_string())
7484
}
7585

86+
/// Parse and cache bin files from the bin field
87+
pub fn parse_bin_files(&self, package_name: &str) -> Vec<(String, String)> {
88+
match &self.bin {
89+
Some(serde_json::Value::Object(obj)) => obj
90+
.iter()
91+
.map(|(k, v)| (k.clone(), v.as_str().unwrap_or_default().to_string()))
92+
.collect(),
93+
Some(serde_json::Value::String(s)) => vec![(package_name.to_string(), s.clone())],
94+
_ => Vec::new(),
95+
}
96+
}
97+
98+
/// Check if package has install scripts
99+
pub fn has_install_scripts(&self) -> bool {
100+
self.has_install_script.unwrap_or(false)
101+
}
102+
76103
/// Convert to PackageNode
77104
pub fn to_package_node(&self, path: &str) -> PackageNode {
78105
let name = self.get_name(path);
@@ -257,4 +284,103 @@ mod tests {
257284
assert_eq!(package_lock.lockfile_version, 3);
258285
assert_eq!(package_lock.packages.len(), 3);
259286
}
287+
288+
#[test]
289+
fn test_lock_package_parse_bin_files() {
290+
// Test with object-style bin
291+
let mut package = LockPackage {
292+
name: Some("test-package".to_string()),
293+
version: Some("1.0.0".to_string()),
294+
bin: Some(serde_json::json!({"cli": "bin/cli.js", "tool": "bin/tool.js"})),
295+
..LockPackage::default()
296+
};
297+
298+
let bin_files = package.parse_bin_files("test-package");
299+
assert_eq!(bin_files.len(), 2);
300+
assert!(bin_files.contains(&("cli".to_string(), "bin/cli.js".to_string())));
301+
assert!(bin_files.contains(&("tool".to_string(), "bin/tool.js".to_string())));
302+
303+
// Test with string-style bin
304+
package.bin = Some(serde_json::json!("index.js"));
305+
let bin_files = package.parse_bin_files("test-package");
306+
assert_eq!(bin_files.len(), 1);
307+
assert_eq!(
308+
bin_files[0],
309+
("test-package".to_string(), "index.js".to_string())
310+
);
311+
312+
// Test with no bin
313+
package.bin = None;
314+
let bin_files = package.parse_bin_files("test-package");
315+
assert_eq!(bin_files.len(), 0);
316+
317+
// Test with invalid bin value
318+
package.bin = Some(serde_json::json!(123));
319+
let bin_files = package.parse_bin_files("test-package");
320+
assert_eq!(bin_files.len(), 0);
321+
}
322+
323+
#[test]
324+
fn test_lock_package_has_install_scripts() {
325+
let mut package = LockPackage {
326+
name: Some("test-package".to_string()),
327+
version: Some("1.0.0".to_string()),
328+
has_install_script: Some(true),
329+
..LockPackage::default()
330+
};
331+
332+
assert!(package.has_install_scripts());
333+
334+
package.has_install_script = Some(false);
335+
assert!(!package.has_install_scripts());
336+
337+
package.has_install_script = None;
338+
assert!(!package.has_install_scripts());
339+
}
340+
341+
#[test]
342+
fn test_lock_package_get_name() {
343+
let mut package = LockPackage {
344+
name: Some("test-package".to_string()),
345+
version: Some("1.0.0".to_string()),
346+
..LockPackage::default()
347+
};
348+
349+
// Test with explicit name
350+
assert_eq!(
351+
package.get_name("node_modules/some-package"),
352+
"test-package"
353+
);
354+
355+
// Test without name (infer from path)
356+
package.name = None;
357+
assert_eq!(package.get_name("node_modules/lodash"), "lodash");
358+
assert_eq!(
359+
package.get_name("node_modules/@scope/package"),
360+
"@scope/package"
361+
);
362+
363+
// Test with empty path
364+
assert_eq!(package.get_name(""), "root");
365+
366+
// Test with complex path
367+
assert_eq!(
368+
package.get_name("node_modules/parent/node_modules/child"),
369+
"child"
370+
);
371+
}
372+
373+
#[test]
374+
fn test_lock_package_get_version() {
375+
let mut package = LockPackage {
376+
name: Some("test-package".to_string()),
377+
version: Some("1.2.3".to_string()),
378+
..LockPackage::default()
379+
};
380+
381+
assert_eq!(package.get_version(), "1.2.3");
382+
383+
package.version = None;
384+
assert_eq!(package.get_version(), "unknown");
385+
}
260386
}

crates/pm/src/service/install.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::pin::Pin;
88
use std::sync::Arc;
99
use tokio::sync::Semaphore;
1010

11-
use crate::cmd::rebuild::rebuild;
1211
use crate::helper::global_bin::get_global_bin_dir;
1312
use crate::helper::lock::{
1413
Package, ensure_package_lock, extract_package_name, group_by_depth, path_to_pkg_name,
@@ -17,6 +16,7 @@ use crate::helper::lock::{
1716
use crate::helper::workspace;
1817
use crate::helper::{is_cpu_compatible, is_os_compatible};
1918
use crate::model::package::PackageInfo;
19+
use crate::service::rebuild::RebuildService;
2020
use crate::util::cache::get_cache_dir;
2121
use crate::util::cloner::clone;
2222
use crate::util::downloader::download;
@@ -400,19 +400,21 @@ impl InstallService {
400400

401401
finish_progress_bar("node_modules cloned");
402402

403+
// Execute rebuild operations based on ignore_scripts parameter
403404
if !ignore_scripts {
404405
log_info(
405406
"Starting to execute dependency hook scripts, you can add --ignore-scripts to skip",
406407
);
407-
rebuild(root_path).await?;
408+
RebuildService::rebuild(&package_lock, root_path, false).await?;
408409
log_info("💫 All dependencies installed successfully");
409-
Ok(())
410410
} else {
411+
log_info("Processing binary files...");
412+
RebuildService::rebuild(&package_lock, root_path, true).await?;
411413
log_info(
412-
"💫 All dependencies installed successfully (you can run 'utoo rebuild' to trigger dependency hooks)",
414+
"💫 All dependencies installed successfully (scripts skipped, binaries linked)",
413415
);
414-
Ok(())
415416
}
417+
Ok(())
416418
}
417419

418420
pub async fn install_global_package(npm_spec: &str, prefix: Option<&str>) -> Result<()> {

crates/pm/src/service/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod http_client;
88
pub mod install;
99
pub mod package;
1010
pub mod package_management;
11+
pub mod rebuild;
1112
pub mod registry;
1213
pub mod script;
1314
pub mod update;

0 commit comments

Comments
 (0)