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}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
59pub struct WorktreeSnapshot {
60 pub root_path: PathBuf,
62 pub worktree_path: PathBuf,
64 pub default_branch: String,
66}
67
68impl From<&Worktree> for WorktreeSnapshot {
69 fn from(context: &Worktree) -> Self {
70 Self {
71 root_path: context.root_path.clone(),
72 worktree_path: context.worktree_path.clone(),
73 default_branch: context.default_branch.clone(),
74 }
75 }
76}
77
78pub fn check(options: CheckOptions) -> Result<CheckReport> {
86 let runtime_policy = RuntimePolicy::from_environment(&options.environment, options.strict)?;
87 let pre_config_strict = runtime_policy.pre_config_strict();
88 let context = context::resolve(&WorktreeOptions {
89 cwd: options.cwd.clone(),
90 root: options.root.clone(),
91 environment: options.environment.clone(),
92 })?;
93
94 if context.root_path == context.worktree_path {
95 if pre_config_strict {
96 return Err(Error::RootWorktreeStrict);
97 }
98
99 return Ok(CheckReport {
100 context: WorktreeSnapshot::from(&context),
101 action: CheckAction::RootWorktreeSkipped,
102 });
103 }
104
105 if options.config.is_none() && !options.no_init_script {
106 let scripts = InitScriptDiscovery::discover(&context);
107
108 if let Some(path) = scripts.executable {
109 return Ok(CheckReport {
110 context: WorktreeSnapshot::from(&context),
111 action: CheckAction::InitScript { path },
112 });
113 }
114 }
115
116 match Config::discover_path(&context, options.config.as_deref())? {
117 Some(path) => {
118 let config = Config::load(&path, &context)?;
119 let plan_options = runtime_policy.resolve(&config.options);
120 ActionPlan::from_manifest(
121 &path,
122 &config,
123 &context,
124 plan_options.into_action_plan_options(),
125 )?;
126
127 Ok(CheckReport {
128 context: WorktreeSnapshot::from(&context),
129 action: CheckAction::Config { path },
130 })
131 }
132 None => {
133 if pre_config_strict {
134 Err(Error::NoConfigDetectedStrict)
135 } else {
136 Ok(CheckReport {
137 context: WorktreeSnapshot::from(&context),
138 action: CheckAction::MissingConfig,
139 })
140 }
141 }
142 }
143}