Skip to main content

dev_prune/commands/
setup.rs

1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2026 VKrishna04
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Handler for `dev-prune setup`.
19//!
20//! The same pass dev-prune runs by itself after an install or an upgrade, available to
21//! run by hand — which is what the installer scripts do, and what the error messages
22//! point people at when an integration was skipped.
23
24use anyhow::Result;
25
26use crate::commands::hook::{self, HookState};
27use crate::config::Registry;
28use crate::daemon;
29use crate::output;
30use crate::setup;
31
32pub fn run(status_only: bool) -> Result<()> {
33    if status_only {
34        return run_status();
35    }
36
37    output::print_header("dev-prune setup");
38    let registry = Registry::load()?;
39    let report = setup::ensure_integrations(&registry);
40    report.print(true);
41    setup::suppress_next_auto_setup();
42
43    println!();
44    if report.needs_attention() {
45        output::print_info("Anything skipped above is optional — dev-prune works without it.");
46    } else {
47        output::print_success("Everything dev-prune needs is in place.");
48    }
49    output::print_info("Remove all of it again with `devp uninstall`.");
50
51    Ok(())
52}
53
54/// Report each integration without touching anything.
55fn run_status() -> Result<()> {
56    output::print_header("dev-prune setup status");
57    let registry = Registry::load()?;
58
59    let alias = std::env::current_exe()
60        .ok()
61        .and_then(|exe| exe.parent().map(|d| d.to_path_buf()))
62        .map(|dir| dir.join(if cfg!(windows) { "devp.exe" } else { "devp" }))
63        .filter(|p| p.exists());
64    println!(
65        "  devp alias:            {}",
66        alias
67            .as_ref()
68            .map(output::clean_path)
69            .unwrap_or_else(|| "not installed".to_string())
70    );
71
72    let skill = setup::skill_path().ok().filter(|p| p.exists());
73    println!(
74        "  SKILL.md:              {}",
75        skill
76            .as_ref()
77            .map(output::clean_path)
78            .unwrap_or_else(|| "not exported".to_string())
79    );
80
81    println!(
82        "  File icons:            {}",
83        if crate::commands::icon::is_registered() {
84            "registered"
85        } else {
86            "not registered"
87        }
88    );
89
90    print!("  Git hooks:             ");
91    if !hook::git_available() {
92        println!("git is not on PATH");
93    } else {
94        match hook::state() {
95            Ok(HookState::Active) => println!("active"),
96            Ok(HookState::Absent) => println!("not installed"),
97            Ok(HookState::Chained { previous, drifted }) if drifted.is_empty() => {
98                println!("active, chained to `{previous}`")
99            }
100            Ok(HookState::Chained { previous, drifted }) => println!(
101                "active, chained to `{previous}` — {} not forwarded ({}); run `devp hook install --chain`",
102                drifted.len(),
103                drifted.join(", ")
104            ),
105            Ok(HookState::Foreign(p)) => println!(
106                "core.hooksPath belongs to `{p}` (install in front of it with `devp hook install --chain`)"
107            ),
108            Err(e) => println!("unknown ({e})"),
109        }
110    }
111
112    println!(
113        "  Background scheduler:  {}",
114        daemon::daemon_status()
115            .map(|s| s.to_string())
116            .unwrap_or_else(|e| format!("unknown ({e})"))
117    );
118
119    println!();
120    println!("  auto_setup  = {}", registry.settings.auto_setup);
121    println!("  auto_hooks  = {}", registry.settings.auto_hooks);
122    println!("  auto_daemon = {}", registry.settings.auto_daemon);
123
124    // Said out loud, because otherwise "auto_setup = true" and nothing ever installing
125    // is a contradiction the user has no way to explain.
126    if let Some(why) = setup::unattended_environment() {
127        println!();
128        output::print_info(&format!(
129            "Unattended installation is off because {why}. `devp setup` still works when asked."
130        ));
131    }
132
133    println!();
134    if setup::setup_is_due() {
135        output::print_info("A setup pass is due. Run `devp setup`.");
136    } else {
137        output::print_info("Install anything missing with `devp setup`.");
138    }
139
140    Ok(())
141}