cli/sys/
profile_commands.rs1use anyhow::{Context, Result, bail};
2
3use crate::colors;
4use crate::config::Config;
5
6use super::bootstrap::item_is_present;
7use super::commands::current_unix_timestamp;
8use super::detect::detect_os_id;
9use super::manifest::load_sys_preset;
10use super::profile::install_sys_profile_loader_with_templates;
11use super::profile_compose::{compose_sys_profiles, enabled_profile_items};
12use super::run_manifest::{SysRunEntry, SysRunManifest};
13use super::{SysItemMode, SysItemStatus};
14
15pub async fn handle_profile_enable(config: &Config, item_id: &str, dry_run: bool) -> Result<()> {
16 handle_profile_state(config, item_id, true, dry_run).await
17}
18
19pub async fn handle_profile_disable(config: &Config, item_id: &str, dry_run: bool) -> Result<()> {
20 handle_profile_state(config, item_id, false, dry_run).await
21}
22
23pub(super) async fn sync_composed_profile(
24 config: &Config,
25) -> Result<Option<super::SysItemOutcome>> {
26 let os_id = detect_os_id().await?;
27 let loaded = load_sys_preset(config, &os_id).await?;
28 let run_manifest = SysRunManifest::load(config.shine_dir()).await?;
29 let enabled_items = enabled_profile_items(&loaded.manifest, &run_manifest.entries, &os_id);
30 let sys_shell: &'static str = config.shell_type.into();
31 let templates =
32 compose_sys_profiles(config, &os_id, &loaded, &enabled_items, sys_shell).await?;
33 install_sys_profile_loader_with_templates(
34 config,
35 &os_id,
36 &loaded.root,
37 sys_shell,
38 false,
39 Some(&templates),
40 )
41 .await
42 .map(Some)
43}
44
45async fn handle_profile_state(
46 config: &Config,
47 item_id: &str,
48 enabled: bool,
49 dry_run: bool,
50) -> Result<()> {
51 crate::config::print_presets_note(config);
52 let os_id = detect_os_id().await?;
53 let loaded = load_sys_preset(config, &os_id).await?;
54 let item = loaded
55 .manifest
56 .items
57 .iter()
58 .find(|item| item.id == item_id)
59 .with_context(|| format!("unknown sys item `{item_id}` for {os_id}"))?;
60 if item.mode != SysItemMode::Init {
61 bail!("managed sys item `{item_id}` has no bootstrap shell integration");
62 }
63 if item.shell.is_empty() {
64 bail!("sys item `{item_id}` declares no shell integration");
65 }
66 if enabled && !item_is_present(config, item).await? {
67 bail!(
68 "sys item `{item_id}` is not currently detected; run `shine sys bootstrap {item_id}` first"
69 );
70 }
71
72 let mut run_manifest = SysRunManifest::load(config.shine_dir()).await?;
73 let existing = run_manifest
74 .entries
75 .iter_mut()
76 .find(|entry| entry.os_id == os_id && entry.item_id == item_id && !entry.managed);
77 match existing {
78 Some(entry) => entry.profile_enabled = enabled,
79 None if enabled => run_manifest.upsert(SysRunEntry {
80 os_id: os_id.clone(),
81 item_id: item.id.clone(),
82 label: item.label.clone(),
83 status: SysItemStatus::AlreadyInstalled,
84 detail: "shell integration enabled after live detection".to_string(),
85 updated_at: current_unix_timestamp().to_string(),
86 managed: false,
87 profile_enabled: true,
88 receipt: None,
89 }),
90 None => {}
91 }
92
93 let enabled_items = enabled_profile_items(&loaded.manifest, &run_manifest.entries, &os_id);
94 let sys_shell: &'static str = config.shell_type.into();
95 let templates =
96 compose_sys_profiles(config, &os_id, &loaded, &enabled_items, sys_shell).await?;
97 let action = if enabled { "enable" } else { "disable" };
98 if dry_run {
99 println!(
100 "{} sys/{item_id}: would {action} shell integration for {sys_shell}",
101 colors::dim("[dry-run]")
102 );
103 return Ok(());
104 }
105
106 let outcome = install_sys_profile_loader_with_templates(
107 config,
108 &os_id,
109 &loaded.root,
110 sys_shell,
111 false,
112 Some(&templates),
113 )
114 .await?;
115 run_manifest.save(config.shine_dir()).await?;
116 println!(
117 "{} sys/{item_id}: shell integration {} ({})",
118 colors::symbol("✓"),
119 if enabled { "enabled" } else { "disabled" },
120 outcome.detail
121 );
122 Ok(())
123}