1use std::path::PathBuf;
2
3use serde::Serialize;
4
5use crate::config::RuntimeOptionOverrides;
6use crate::context;
7use crate::{
8 ActionPlan, Config, EnvironmentInput, Error, InitScriptDiscovery, Result, Worktree,
9 WorktreeOptions,
10};
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct CheckOptions {
15 pub cwd: Option<PathBuf>,
17 pub root: Option<PathBuf>,
19 pub environment: EnvironmentInput,
21 pub config: Option<PathBuf>,
23 pub no_init_script: bool,
25 pub strict: bool,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
31#[serde(tag = "kind", rename_all = "snake_case")]
32pub enum CheckAction {
33 MissingConfig,
35 RootWorktreeSkipped,
37 InitScript {
39 path: PathBuf,
41 },
42 Config {
44 path: PathBuf,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51pub struct CheckReport {
52 pub context: WorktreeSnapshot,
54 pub action: CheckAction,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
60pub struct WorktreeSnapshot {
61 pub root_path: PathBuf,
63 pub worktree_path: PathBuf,
65 pub default_branch: String,
67}
68
69impl From<&Worktree> for WorktreeSnapshot {
70 fn from(context: &Worktree) -> Self {
71 Self {
72 root_path: context.root_path.clone(),
73 worktree_path: context.worktree_path.clone(),
74 default_branch: context.default_branch.clone(),
75 }
76 }
77}
78
79pub fn check(options: CheckOptions) -> Result<CheckReport> {
87 let env_options = RuntimeOptionOverrides::from_environment(&options.environment)?;
88 let pre_config_strict = env_options.pre_config_strict(options.strict);
89 let context = context::resolve(&WorktreeOptions {
90 cwd: options.cwd.clone(),
91 root: options.root.clone(),
92 environment: options.environment.clone(),
93 })?;
94
95 if context.root_path == context.worktree_path {
96 if pre_config_strict {
97 return Err(Error::RootWorktreeStrict);
98 }
99
100 return Ok(CheckReport {
101 context: WorktreeSnapshot::from(&context),
102 action: CheckAction::RootWorktreeSkipped,
103 });
104 }
105
106 if options.config.is_none() && !options.no_init_script {
107 let scripts = InitScriptDiscovery::discover(&context);
108
109 if let Some(path) = scripts.executable {
110 return Ok(CheckReport {
111 context: WorktreeSnapshot::from(&context),
112 action: CheckAction::InitScript { path },
113 });
114 }
115 }
116
117 match Config::discover_path(&context, options.config.as_deref())? {
118 Some(path) => {
119 let config = Config::load(&path, &context)?;
120 let plan_options = env_options.resolve(&config.options, options.strict);
121 ActionPlan::from_manifest(&path, &config, &context, plan_options.into())?;
122
123 Ok(CheckReport {
124 context: WorktreeSnapshot::from(&context),
125 action: CheckAction::Config { path },
126 })
127 }
128 None => {
129 if pre_config_strict {
130 Err(Error::NoConfigDetectedStrict)
131 } else {
132 Ok(CheckReport {
133 context: WorktreeSnapshot::from(&context),
134 action: CheckAction::MissingConfig,
135 })
136 }
137 }
138 }
139}