From 3ac6fcbd4fd7963a8af6cbb78bb45a60c6030431 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Tue, 19 Nov 2024 19:56:52 -0500 Subject: [PATCH] support glob pattern for task runner (#31) --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- src/config.rs | 16 +++++++++++++++- src/tasks.rs | 20 +++++++++++++++++--- src/workspace.rs | 17 ++++++++--------- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 67be7dd..da23a09 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Options: - `files` - List affected files - `projects` - List affected projects - `tasks` - List defined tasks -- `run [task]` - Run a task on affected files or projects +- `run [task|glob]` - Run a task on affected files or projects For more information on a command, use the `help` command. @@ -136,10 +136,17 @@ tasks: - npx prettier --check {files} ``` -Example: +Examples: ```bash +# view the tasks defined in the configuration file +affected view tasks + +# run the task named 'lint' affected run lint + +# run tasks by a glob pattern (e.g. 'project:1', 'project:2', etc.) +affected run "project:*" ``` ### File Separators @@ -160,6 +167,41 @@ tasks: The default separator is a space. +### Using Glob Patterns + +Glob patterns can be used to run multiple tasks on different file types. + +```yaml +# .affected.yml +base: develop +tasks: + - name: echo:1 + description: Echoes the affected files (example) + patterns: + - '*' + commands: + - echo 'one' + - name: echo:2 + description: Echoes the affected files (example) + patterns: + - '*' + commands: + - echo 'two' +``` + +Run the tasks: + +```bash +affected run "echo:*" +``` + +The output is: + +```bash +one +two +``` + ### Environment Variables Environment variables can be set in the `.env` file in the root of the repository. @@ -228,6 +270,8 @@ LOG_LEVEL=DEBUG affected run lint ## Recipes +Below are some examples of tasks that can be defined in the `.affected.yml` file. + ### Run ESLint on affected files ```yaml diff --git a/src/config.rs b/src/config.rs index 5ff918e..7eb9237 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use globset::Glob; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use std::fs::File; @@ -18,6 +19,19 @@ impl Config { .as_ref() .and_then(|tasks| tasks.iter().find(|task| task.name == task_name)) } + + pub fn get_tasks(&self, pattern: &str) -> Vec<&Task> { + let glob = Glob::new(pattern).unwrap().compile_matcher(); + self.tasks + .as_ref() + .map(|tasks| { + tasks + .iter() + .filter(|task| glob.is_match(&task.name)) + .collect() + }) + .unwrap_or_default() + } } impl Default for Config { @@ -45,7 +59,7 @@ impl Default for Config { } #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct Task { pub name: String, pub description: Option, diff --git a/src/tasks.rs b/src/tasks.rs index 74a52bd..2c1c288 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,3 +1,4 @@ +use crate::config::Task; use crate::workspace::Workspace; use anyhow::{bail, Context, Result}; use globset::{Glob, GlobSetBuilder}; @@ -5,10 +6,23 @@ use log::debug; use std::process::Stdio; use tokio::process::Command; -pub async fn run_task_by_name(workspace: &Workspace, task_name: &str) -> Result<()> { - debug!("Running task: {}", task_name); +pub async fn run_tasks(workspace: &Workspace, pattern: &str) -> Result<()> { let config = workspace.config().context("No configuration found")?; - let task = config.get_task(task_name).context("Task not found")?; + let tasks = config.get_tasks(pattern); + + if tasks.is_empty() { + println!("No tasks matched the pattern"); + return Ok(()); + } + + for task in tasks { + run_task(workspace, task).await?; + } + + Ok(()) +} + +async fn run_task(workspace: &Workspace, task: &Task) -> Result<()> { let file_paths = workspace.affected_files()?; // filter out files that exist on the filesystem diff --git a/src/workspace.rs b/src/workspace.rs index 2568808..86634e9 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -1,3 +1,4 @@ +use crate::config::Task; use crate::graph::{NodeType, ProjectNode}; use crate::nx::NxProject; use crate::projects::Project; @@ -97,18 +98,16 @@ impl Workspace { } /// Returns a list of tasks defined in the configuration - pub fn tasks(&self) -> Vec { + pub fn tasks(&self) -> Vec<&Task> { let config = self.config.as_ref().expect("Configuration not loaded"); - - if let Some(tasks) = &config.tasks { - tasks.iter().map(|task| task.name.clone()).collect() - } else { - vec![] - } + config + .tasks + .as_ref() + .map_or_else(Vec::new, |tasks| tasks.iter().collect()) } - pub async fn run_task(&self, task_name: &str) -> Result<()> { - crate::tasks::run_task_by_name(self, task_name).await + pub async fn run_task(&self, pattern: &str) -> Result<()> { + crate::tasks::run_tasks(self, pattern).await } pub fn is_project_dir(path: &Path) -> bool {