mirror of
https://github.com/kristoferssolo/project-finder.git
synced 2025-10-21 19:50:35 +00:00
refactor: mitigate some issues
This commit is contained in:
parent
73e1b44312
commit
724e0fffd3
105
Cargo.lock
generated
105
Cargo.lock
generated
@ -180,6 +180,95 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
@ -318,6 +407,12 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.94"
|
version = "1.0.94"
|
||||||
@ -333,6 +428,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"futures",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -401,6 +497,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
|
|||||||
@ -16,6 +16,7 @@ readme = "README.md"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
futures = "0.3"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|||||||
@ -93,6 +93,7 @@ pub async fn grep_file(deps: &Dependencies, file: &Path, pattern: &str) -> Resul
|
|||||||
let mut cmd = Command::new(&deps.rg_path);
|
let mut cmd = Command::new(&deps.rg_path);
|
||||||
|
|
||||||
cmd.arg("-q") // quiet mode, just return exit code
|
cmd.arg("-q") // quiet mode, just return exit code
|
||||||
|
.arg("-e") // explicitly specify pattern
|
||||||
.arg(pattern)
|
.arg(pattern)
|
||||||
.arg(file);
|
.arg(file);
|
||||||
|
|
||||||
|
|||||||
@ -18,4 +18,8 @@ pub struct Config {
|
|||||||
/// Show verbose output
|
/// Show verbose output
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
|
||||||
|
/// Maximum number of results to return
|
||||||
|
#[clap(short = 'n', long, default_value = "0")]
|
||||||
|
pub max_results: usize,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ impl Dependencies {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
info!("Found fd at: {}", fd_path.display());
|
info!("Found fd at: {}", fd_path.display());
|
||||||
info!("Found rupgrep at: {}", rg_path.display());
|
info!("Found ripgrep at: {}", rg_path.display());
|
||||||
|
|
||||||
Ok(Self::new(
|
Ok(Self::new(
|
||||||
fd_path.to_string_lossy(),
|
fd_path.to_string_lossy(),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use crate::{
|
|||||||
dependencies::Dependencies,
|
dependencies::Dependencies,
|
||||||
errors::{ProjectFinderError, Result},
|
errors::{ProjectFinderError, Result},
|
||||||
};
|
};
|
||||||
|
use futures::future::join_all;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -46,7 +47,7 @@ impl ProjectFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_projects(&self) -> Result<Vec<PathBuf>> {
|
pub async fn find_projects(&self) -> Result<Vec<PathBuf>> {
|
||||||
// Process each search directory
|
let semaphore = Arc::new(tokio::sync::Semaphore::new(8)); // Limit to 8 concurrent tasks
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
|
|
||||||
for path in &self.config.paths {
|
for path in &self.config.paths {
|
||||||
@ -61,27 +62,43 @@ impl ProjectFinder {
|
|||||||
|
|
||||||
let finder_clone = self.clone();
|
let finder_clone = self.clone();
|
||||||
let path_clone = path_buf.clone();
|
let path_clone = path_buf.clone();
|
||||||
|
let semaphore_clone = Arc::clone(&semaphore);
|
||||||
|
|
||||||
// Spawn a task for each directory
|
// Spawn a task for each directory with semaphore permit
|
||||||
let handle =
|
let handle = tokio::spawn(async move {
|
||||||
tokio::spawn(async move { finder_clone.process_directory(&path_clone).await });
|
let _permit = semaphore_clone.acquire().await.map_err(|e| {
|
||||||
|
ProjectFinderError::CommandExecutionFailed(format!(
|
||||||
|
"Failed to aquire semaphore: {e}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
finder_clone.process_directory(&path_clone).await
|
||||||
|
});
|
||||||
|
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all tasks to complete
|
let handle_results = join_all(handles).await;
|
||||||
for handle in handles {
|
|
||||||
match handle.await {
|
let mut errors = handle_results
|
||||||
Ok(result) => {
|
.into_iter()
|
||||||
// Propagate internal errors
|
.filter_map(|handle_result| match handle_result {
|
||||||
if let Err(e) = result {
|
Ok(task_result) => task_result.err().map(|e| {
|
||||||
debug!("Task failed: {}", e);
|
debug!("Task failed: {}", e);
|
||||||
}
|
e
|
||||||
}
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Task join error: {}", e);
|
debug!("Task join error: {}", e);
|
||||||
|
Some(ProjectFinderError::CommandExecutionFailed(format!(
|
||||||
|
"Task panicked: {e}",
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Return first error if any occurred
|
||||||
|
if !errors.is_empty() && errors.len() == self.config.paths.len() {
|
||||||
|
// Only fail if all tasks failed
|
||||||
|
return Err(errors.remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return sorted results
|
// Return sorted results
|
||||||
@ -91,6 +108,12 @@ impl ProjectFinder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
projects.sort();
|
projects.sort();
|
||||||
|
|
||||||
|
// Apply max_results if set
|
||||||
|
if self.config.max_results > 0 && projects.len() > self.config.max_results {
|
||||||
|
projects.truncate(self.config.max_results);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(projects)
|
Ok(projects)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,12 +171,23 @@ impl ProjectFinder {
|
|||||||
// Find project root
|
// Find project root
|
||||||
let project_root = self.find_project_root(dir, &marker_type).await?;
|
let project_root = self.find_project_root(dir, &marker_type).await?;
|
||||||
|
|
||||||
// Check if it's a subdirectory of an already discovered project
|
// Improved nested project detection
|
||||||
|
// Only ignore if it's a subproject of the same type (prevents ignoring
|
||||||
|
// valid nested projects of different types)
|
||||||
let mut should_add = true;
|
let mut should_add = true;
|
||||||
{
|
{
|
||||||
let projects = self.discovered_projects.lock().await;
|
let projects = self.discovered_projects.lock().await;
|
||||||
for known_project in projects.iter() {
|
for known_project in projects.iter() {
|
||||||
if project_root.starts_with(known_project) && project_root != *known_project {
|
// Check if this is a direct parent (not just any ancestor)
|
||||||
|
let is_direct_parent = project_root
|
||||||
|
.parent()
|
||||||
|
.is_some_and(|parent| parent == known_project);
|
||||||
|
|
||||||
|
// Only exclude if it's a subdirectory and has the same marker type
|
||||||
|
// or if it's exactly the same directory
|
||||||
|
if project_root == *known_project
|
||||||
|
|| project_root.starts_with(known_project) && !is_direct_parent
|
||||||
|
{
|
||||||
should_add = false;
|
should_add = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -293,14 +327,24 @@ impl ProjectFinder {
|
|||||||
|
|
||||||
// Define workspace patterns to check
|
// Define workspace patterns to check
|
||||||
let workspace_patterns = [
|
let workspace_patterns = [
|
||||||
(dir.join("package.json"), r#""workspaces""#),
|
(dir.join("package.json"), r#"("workspaces"|"workspace")"#),
|
||||||
(dir.join("deno.json"), r#""workspaces""#),
|
(dir.join("deno.json"), r#"("workspaces"|"imports")"#),
|
||||||
(dir.join("deno.jsonc"), r#""workspaces""#),
|
(dir.join("deno.jsonc"), r#"("workspaces"|"imports")"#),
|
||||||
(dir.join("bunfig.toml"), r"workspaces"),
|
(dir.join("bunfig.toml"), r"workspaces"),
|
||||||
|
(dir.join("Cargo.toml"), r"^\[workspace\]"),
|
||||||
|
(dir.join("rush.json"), r"."),
|
||||||
|
(dir.join("nx.json"), r"."),
|
||||||
|
(dir.join("turbo.json"), r"."),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Files that indicate workspaces just by existing
|
// Files that indicate workspaces just by existing
|
||||||
let workspace_files = [dir.join("pnpm-workspace.yaml"), dir.join("lerna.json")];
|
let workspace_files = [
|
||||||
|
dir.join("pnpm-workspace.yaml"),
|
||||||
|
dir.join("lerna.json"),
|
||||||
|
dir.join("yarn.lock"), // Common in yarn workspaces
|
||||||
|
dir.join(".yarnrc.yml"), // Yarn 2+ workspaces
|
||||||
|
dir.join("workspace.json"), // Generic workspace file
|
||||||
|
];
|
||||||
|
|
||||||
// Check for workspace by pattern matching
|
// Check for workspace by pattern matching
|
||||||
for (file, pattern) in &workspace_patterns {
|
for (file, pattern) in &workspace_patterns {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user