Skip to main content

queue_ast_hop/
cli.rs

1use crate::report::{print_check, print_edges, print_related, Format};
2use anyhow::{Context, Result};
3use clap::{Parser, Subcommand, ValueEnum};
4use no_mistakes_core::cli::{init_rayon_threads, resolve_root, JobsArg};
5use no_mistakes_core::queue::{analyze_project, related, RelatedDirection};
6use std::path::PathBuf;
7use std::process::ExitCode;
8
9#[derive(Parser)]
10#[command(author, version, about)]
11pub(crate) struct Cli {
12    /// Project root directory.
13    #[arg(long, default_value = ".", global = true)]
14    root: PathBuf,
15    /// Path to tsconfig.json for path alias resolution.
16    #[arg(long, global = true)]
17    tsconfig: Option<PathBuf>,
18    /// Filter to source files matching this glob. Can be repeated.
19    #[arg(long = "filter", global = true)]
20    filters: Vec<String>,
21    /// Maximum edge traversal depth for the edges command when roots are provided.
22    /// Defaults to 1 when roots are provided, and unlimited otherwise.
23    #[arg(long, alias = "max-depth", global = true)]
24    depth: Option<usize>,
25    /// Output format.
26    #[arg(long, value_enum, global = true, conflicts_with = "json")]
27    format: Option<Format>,
28    /// Shorthand for --format json.
29    #[arg(long, global = true, conflicts_with = "format")]
30    json: bool,
31    /// Emit phase timings to stderr.
32    #[arg(long, global = true)]
33    timings: bool,
34    #[command(flatten)]
35    jobs: JobsArg,
36    #[command(subcommand)]
37    command: Command,
38}
39
40#[derive(Subcommand)]
41enum Command {
42    /// Print queue dependency edges.
43    Edges {
44        /// Only show edges whose source exactly matches these files/nodes.
45        files: Vec<String>,
46    },
47    /// Print files/nodes related to the given files/nodes.
48    Related {
49        /// Files or queue job nodes such as queues.ts#sendWelcome.
50        #[arg(required = true)]
51        files: Vec<String>,
52        /// Traverse dependencies, dependents, or both directions.
53        #[arg(long, value_enum, default_value = "both")]
54        direction: DirectionArg,
55    },
56    /// Check for unmatched static producers and workers.
57    Check,
58}
59
60#[derive(ValueEnum, Clone, Copy)]
61enum DirectionArg {
62    Deps,
63    Dependents,
64    Both,
65}
66
67pub fn run_cli() -> Result<ExitCode> {
68    let cli = Cli::parse();
69    init_rayon_threads(cli.jobs);
70    let base = std::env::current_dir().context("reading current directory")?;
71    let root = resolve_root(&cli.root, &base);
72    if cli.timings {
73        eprintln!("search: 0.000ms");
74    }
75    let report = analyze_project(&root, cli.tsconfig.as_deref(), &cli.filters)?;
76    let format = cli.format.unwrap_or({
77        if cli.json {
78            Format::Json
79        } else {
80            Format::Human
81        }
82    });
83    match &cli.command {
84        Command::Edges { files } => {
85            print_edges(&report, files, cli.depth, format)?;
86            Ok(ExitCode::SUCCESS)
87        }
88        Command::Related { files, direction } => {
89            let edges = related(&report, files, (*direction).into());
90            print_related(files, &edges, format)?;
91            Ok(ExitCode::SUCCESS)
92        }
93        Command::Check => {
94            print_check(&report, format)?;
95            Ok(if report.check.is_empty() {
96                ExitCode::SUCCESS
97            } else {
98                ExitCode::from(1)
99            })
100        }
101    }
102}
103
104impl From<DirectionArg> for RelatedDirection {
105    fn from(value: DirectionArg) -> Self {
106        match value {
107            DirectionArg::Deps => RelatedDirection::Deps,
108            DirectionArg::Dependents => RelatedDirection::Dependents,
109            DirectionArg::Both => RelatedDirection::Both,
110        }
111    }
112}