task_runner_detector/
lib.rs

1//! Task Runner Detector - Discover and run tasks from various config files
2//!
3//! This crate scans a directory tree and discovers runnable tasks from common
4//! task runner configuration files like package.json, Makefile, Cargo.toml, etc.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use task_runner_detector::scan;
10//!
11//! let runners = scan(".").unwrap();
12//! for runner in runners {
13//!     println!("{:?} @ {}", runner.runner_type, runner.config_path.display());
14//!     for task in &runner.tasks {
15//!         println!("  {} -> {}", task.name, task.command);
16//!     }
17//! }
18//! ```
19
20mod parsers;
21mod scanner;
22
23use std::path::PathBuf;
24use thiserror::Error;
25
26pub use scanner::{scan, scan_streaming, scan_with_options, ScanOptions};
27
28/// The type of task runner detected
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
30#[serde(rename_all = "lowercase")]
31pub enum RunnerType {
32    Npm,
33    Bun,
34    Yarn,
35    Pnpm,
36    Make,
37    Cargo,
38    Flutter,
39    Dart,
40    Turbo,
41    Poetry,
42    Pdm,
43    Just,
44    Deno,
45    Maven,
46    DotNet,
47}
48
49impl RunnerType {
50    /// Returns a human-readable display name for the runner type
51    pub fn display_name(&self) -> &'static str {
52        match self {
53            RunnerType::Npm => "npm",
54            RunnerType::Bun => "bun",
55            RunnerType::Yarn => "yarn",
56            RunnerType::Pnpm => "pnpm",
57            RunnerType::Make => "make",
58            RunnerType::Cargo => "cargo",
59            RunnerType::Flutter => "flutter",
60            RunnerType::Dart => "dart",
61            RunnerType::Turbo => "turbo",
62            RunnerType::Poetry => "poetry",
63            RunnerType::Pdm => "pdm",
64            RunnerType::Just => "just",
65            RunnerType::Deno => "deno",
66            RunnerType::Maven => "mvn",
67            RunnerType::DotNet => "dotnet",
68        }
69    }
70
71    /// Get an icon/emoji for the runner type
72    pub fn icon(&self) -> &'static str {
73        match self {
74            RunnerType::Npm => "๐Ÿ“ฆ",
75            RunnerType::Bun => "๐ŸฅŸ",
76            RunnerType::Yarn => "๐Ÿงถ",
77            RunnerType::Pnpm => "๐Ÿ“ฆ",
78            RunnerType::Make => "๐Ÿ”จ",
79            RunnerType::Cargo => "๐Ÿฆ€",
80            RunnerType::Flutter => "๐Ÿ’™",
81            RunnerType::Dart => "๐ŸŽฏ",
82            RunnerType::Turbo => "โšก",
83            RunnerType::Poetry => "๐Ÿ",
84            RunnerType::Pdm => "๐Ÿ",
85            RunnerType::Just => "๐Ÿ“œ",
86            RunnerType::Deno => "๐Ÿฆ•",
87            RunnerType::Maven => "๐Ÿชถ",
88            RunnerType::DotNet => "๐ŸŸฃ",
89        }
90    }
91
92    /// Get a suggested terminal color for this runner type
93    pub fn color_code(&self) -> u8 {
94        match self {
95            RunnerType::Npm => 1,     // Red
96            RunnerType::Bun => 3,     // Yellow
97            RunnerType::Yarn => 4,    // Blue
98            RunnerType::Pnpm => 3,    // Yellow
99            RunnerType::Make => 2,    // Green
100            RunnerType::Cargo => 1,   // Red
101            RunnerType::Flutter => 6, // Cyan
102            RunnerType::Dart => 6,    // Cyan
103            RunnerType::Turbo => 5,   // Magenta
104            RunnerType::Poetry => 2,  // Green
105            RunnerType::Pdm => 2,     // Green
106            RunnerType::Just => 3,    // Yellow
107            RunnerType::Deno => 2,    // Green
108            RunnerType::Maven => 1,   // Red
109            RunnerType::DotNet => 5,  // Magenta
110        }
111    }
112}
113
114impl std::fmt::Display for RunnerType {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", self.display_name())
117    }
118}
119
120/// A single task that can be run
121#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
122pub struct Task {
123    /// The name of the task (e.g., "build", "test", "dev")
124    pub name: String,
125    /// The full command to run (e.g., "npm run build", "make test")
126    pub command: String,
127    /// Optional description of what the task does
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub description: Option<String>,
130    /// The actual script content (e.g., the shell command in package.json scripts)
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub script: Option<String>,
133}
134
135/// A task runner configuration file with its discovered tasks
136#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
137pub struct TaskRunner {
138    /// Path to the config file (e.g., "apps/mobile/pubspec.yaml")
139    pub config_path: PathBuf,
140    /// The type of task runner
141    pub runner_type: RunnerType,
142    /// List of tasks discovered in the config file
143    pub tasks: Vec<Task>,
144}
145
146/// Errors that can occur during scanning
147#[derive(Error, Debug)]
148pub enum ScanError {
149    #[error("IO error: {0}")]
150    Io(#[from] std::io::Error),
151
152    #[error("Failed to parse {path}: {message}")]
153    ParseError { path: PathBuf, message: String },
154
155    #[error("Walk error: {0}")]
156    WalkError(#[from] ignore::Error),
157}
158
159/// Result type for scan operations
160pub type ScanResult<T> = Result<T, ScanError>;