1pub mod mnemonic;
6pub mod project;
7pub mod steps;
8
9pub use mnemonic::{
10 check_deployer_mnemonic, inspect_settings_file, is_mnemonic_placeholder,
11 is_public_devnet_mnemonic, parse_deployer_mnemonic, settings_relative_path,
12 validate_mnemonic_word_format, MnemonicCheck, ParsedDeployerMnemonic, PUBLIC_DEVNET_MNEMONICS,
13};
14pub use project::{
15 default_config_toml, enter_scaffold_root, find_init_root, find_scaffold_root, load_config,
16 project_root, resolve_scaffold_root, validate_network, StacksdappConfig, CONFIG_FILE,
17};
18pub use steps::{
19 begin_step, grey, kv, lavender, mint, print_banner, println_human_safe, rule, step_ok, LiveStep,
20};
21
22use colored::control;
23use serde::Serialize;
24use std::io::{self, IsTerminal, Write};
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::sync::OnceLock;
27
28static SHELL: OnceLock<Shell> = OnceLock::new();
29static JSON_EMITTED: AtomicBool = AtomicBool::new(false);
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum ColorMode {
33 Auto,
34 Always,
35 Never,
36}
37
38impl ColorMode {
39 pub fn parse(s: &str) -> Option<Self> {
40 match s.to_ascii_lowercase().as_str() {
41 "auto" => Some(Self::Auto),
42 "always" => Some(Self::Always),
43 "never" => Some(Self::Never),
44 _ => None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Format {
51 Human,
52 Json,
53}
54
55#[derive(Debug, Clone)]
56pub struct Shell {
57 pub verbosity: u8,
58 pub quiet: bool,
59 pub format: Format,
60 pub color: ColorMode,
61}
62
63impl Default for Shell {
64 fn default() -> Self {
65 Self {
66 verbosity: 0,
67 quiet: false,
68 format: Format::Human,
69 color: ColorMode::Auto,
70 }
71 }
72}
73
74pub fn init(shell: Shell) {
76 apply_color(shell.color);
77 let _ = SHELL.set(shell);
78}
79
80fn apply_color(mode: ColorMode) {
81 match mode {
82 ColorMode::Always => control::set_override(true),
83 ColorMode::Never => control::set_override(false),
84 ColorMode::Auto => {
85 if !io::stdout().is_terminal() && !io::stderr().is_terminal() {
87 control::set_override(false);
88 } else {
89 control::unset_override();
90 }
91 }
92 }
93}
94
95pub fn get() -> &'static Shell {
96 SHELL.get_or_init(Shell::default)
97}
98
99pub fn verbosity() -> u8 {
100 get().verbosity
101}
102
103pub fn is_quiet() -> bool {
104 get().quiet || get().format == Format::Json
105}
106
107pub fn is_json() -> bool {
108 get().format == Format::Json
109}
110
111pub fn status(msg: impl AsRef<str>) {
113 if is_quiet() {
114 return;
115 }
116 println!("{}", msg.as_ref());
117}
118
119pub fn warn(msg: impl AsRef<str>) {
121 if is_quiet() {
122 return;
123 }
124 eprintln!("{}", msg.as_ref());
125}
126
127pub fn error(msg: impl AsRef<str>) {
129 if is_json() {
130 return;
131 }
132 eprintln!("{}", msg.as_ref());
133}
134
135pub fn debug(level: u8, msg: impl AsRef<str>) {
137 if is_quiet() || verbosity() < level {
138 return;
139 }
140 eprintln!("{}", msg.as_ref());
141}
142
143pub fn emit_json<T: Serialize>(value: &T) {
145 if !is_json() {
146 return;
147 }
148 match serde_json::to_string(value) {
149 Ok(s) => {
150 println!("{s}");
151 let _ = io::stdout().flush();
152 JSON_EMITTED.store(true, Ordering::SeqCst);
153 }
154 Err(e) => eprintln!("{{\"ok\":false,\"error\":\"json serialize failed: {e}\"}}"),
155 }
156}
157
158pub fn json_already_emitted() -> bool {
160 JSON_EMITTED.load(Ordering::SeqCst)
161}
162
163pub fn emit_error_json(command: &str, message: &str) {
165 if !is_json() || json_already_emitted() {
166 return;
167 }
168 let payload = serde_json::json!({
169 "ok": false,
170 "command": command,
171 "error": message,
172 });
173 emit_json(&payload);
174}
175
176pub fn println_human(msg: impl AsRef<str>) {
178 status(msg);
179}