1use std::path::PathBuf;
2
3use serde::Serialize;
4
5use crate::context;
6use crate::{
7 ActionPlan, Config, EnvironmentInput, Error, InitScriptDiscovery, Result, RuntimePolicy,
8 Worktree, WorktreeOptions,
9};
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct CheckOptions {
14 pub cwd: Option<PathBuf>,
16 pub root: Option<PathBuf>,
18 pub environment: EnvironmentInput,
20 pub config: Option<PathBuf>,
22 pub no_init_script: bool,
24 pub strict: bool,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
30#[serde(tag = "kind", rename_all = "snake_case")]
31pub enum CheckAction {
32 MissingConfig,
34 RootWorktreeSkipped,
36 InitScript {
38 path: PathBuf,
40 },
41 Config {
43 path: PathBuf,
45 },
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
50pub struct CheckReport {
51 pub context: WorktreeSnapshot,
53 pub action: CheckAction,
55 pub warnings: Vec<String>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
63pub struct WorktreeSnapshot {
64 pub root_path: PathBuf,
66 pub worktree_path: PathBuf,
68 pub default_branch: String,
70}
71
72impl From<&Worktree> for WorktreeSnapshot {
73 fn from(context: &Worktree) -> Self {
74 Self {
75 root_path: context.root_path.clone(),
76 worktree_path: context.worktree_path.clone(),
77 default_branch: context.default_branch.clone(),
78 }
79 }
80}
81
82pub fn check(options: CheckOptions) -> Result<CheckReport> {
90 let runtime_policy = RuntimePolicy::from_environment(&options.environment, options.strict)?;
91 let pre_config_strict = runtime_policy.pre_config_strict();
92 let context = context::resolve(&WorktreeOptions {
93 cwd: options.cwd.clone(),
94 root: options.root.clone(),
95 environment: options.environment.clone(),
96 })?;
97
98 if context.root_path == context.worktree_path {
99 if pre_config_strict {
100 return Err(Error::RootWorktreeStrict);
101 }
102
103 return Ok(CheckReport {
104 context: WorktreeSnapshot::from(&context),
105 action: CheckAction::RootWorktreeSkipped,
106 warnings: Vec::new(),
107 });
108 }
109
110 if options.config.is_none() && !options.no_init_script {
111 let scripts = InitScriptDiscovery::discover(&context);
112
113 if let Some(path) = scripts.executable {
114 return Ok(CheckReport {
115 context: WorktreeSnapshot::from(&context),
116 action: CheckAction::InitScript { path },
117 warnings: Vec::new(),
118 });
119 }
120 }
121
122 match Config::discover_path(&context, options.config.as_deref())? {
123 Some(path) => {
124 let config = Config::load(&path, &context)?;
125 let plan_options = runtime_policy.resolve(&config.options);
126 let plan = ActionPlan::from_manifest(
127 &path,
128 &config,
129 &context,
130 plan_options.into_action_plan_options(),
131 )?;
132
133 Ok(CheckReport {
134 context: WorktreeSnapshot::from(&context),
135 action: CheckAction::Config { path },
136 warnings: plan.warnings().iter().map(ToString::to_string).collect(),
137 })
138 }
139 None => {
140 if pre_config_strict {
141 Err(Error::NoConfigDetectedStrict)
142 } else {
143 Ok(CheckReport {
144 context: WorktreeSnapshot::from(&context),
145 action: CheckAction::MissingConfig,
146 warnings: Vec::new(),
147 })
148 }
149 }
150 }
151}