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}
46
47impl RunnerType {
48    /// Returns a human-readable display name for the runner type
49    pub fn display_name(&self) -> &'static str {
50        match self {
51            RunnerType::Npm => "npm",
52            RunnerType::Bun => "bun",
53            RunnerType::Yarn => "yarn",
54            RunnerType::Pnpm => "pnpm",
55            RunnerType::Make => "make",
56            RunnerType::Cargo => "cargo",
57            RunnerType::Flutter => "flutter",
58            RunnerType::Dart => "dart",
59            RunnerType::Turbo => "turbo",
60            RunnerType::Poetry => "poetry",
61            RunnerType::Pdm => "pdm",
62            RunnerType::Just => "just",
63            RunnerType::Deno => "deno",
64        }
65    }
66
67    /// Get an icon/emoji for the runner type
68    pub fn icon(&self) -> &'static str {
69        match self {
70            RunnerType::Npm => "๐Ÿ“ฆ",
71            RunnerType::Bun => "๐ŸฅŸ",
72            RunnerType::Yarn => "๐Ÿงถ",
73            RunnerType::Pnpm => "๐Ÿ“ฆ",
74            RunnerType::Make => "๐Ÿ”จ",
75            RunnerType::Cargo => "๐Ÿฆ€",
76            RunnerType::Flutter => "๐Ÿ’™",
77            RunnerType::Dart => "๐ŸŽฏ",
78            RunnerType::Turbo => "โšก",
79            RunnerType::Poetry => "๐Ÿ",
80            RunnerType::Pdm => "๐Ÿ",
81            RunnerType::Just => "๐Ÿ“œ",
82            RunnerType::Deno => "๐Ÿฆ•",
83        }
84    }
85
86    /// Get a suggested terminal color for this runner type
87    pub fn color_code(&self) -> u8 {
88        match self {
89            RunnerType::Npm => 1,     // Red
90            RunnerType::Bun => 3,     // Yellow
91            RunnerType::Yarn => 4,    // Blue
92            RunnerType::Pnpm => 3,    // Yellow
93            RunnerType::Make => 2,    // Green
94            RunnerType::Cargo => 1,   // Red
95            RunnerType::Flutter => 6, // Cyan
96            RunnerType::Dart => 6,    // Cyan
97            RunnerType::Turbo => 5,   // Magenta
98            RunnerType::Poetry => 2,  // Green
99            RunnerType::Pdm => 2,     // Green
100            RunnerType::Just => 3,    // Yellow
101            RunnerType::Deno => 2,    // Green
102        }
103    }
104}
105
106impl std::fmt::Display for RunnerType {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{}", self.display_name())
109    }
110}
111
112/// A single task that can be run
113#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
114pub struct Task {
115    /// The name of the task (e.g., "build", "test", "dev")
116    pub name: String,
117    /// The full command to run (e.g., "npm run build", "make test")
118    pub command: String,
119    /// Optional description of what the task does
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub description: Option<String>,
122    /// The actual script content (e.g., the shell command in package.json scripts)
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub script: Option<String>,
125}
126
127/// A task runner configuration file with its discovered tasks
128#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
129pub struct TaskRunner {
130    /// Path to the config file (e.g., "apps/mobile/pubspec.yaml")
131    pub config_path: PathBuf,
132    /// The type of task runner
133    pub runner_type: RunnerType,
134    /// List of tasks discovered in the config file
135    pub tasks: Vec<Task>,
136}
137
138/// Errors that can occur during scanning
139#[derive(Error, Debug)]
140pub enum ScanError {
141    #[error("IO error: {0}")]
142    Io(#[from] std::io::Error),
143
144    #[error("Failed to parse {path}: {message}")]
145    ParseError { path: PathBuf, message: String },
146
147    #[error("Walk error: {0}")]
148    WalkError(#[from] ignore::Error),
149}
150
151/// Result type for scan operations
152pub type ScanResult<T> = Result<T, ScanError>;