Skip to content

Commit f84a7b0

Browse files
feat: add codspeed auth status, codspeed setup status, and codspeed status commands
- `codspeed auth status`: checks token validity via API, detects repository from git remote, and verifies it exists on CodSpeed - `codspeed setup status`: shows tool installation status for each executor via the new `tool_status()` trait method - `codspeed status`: combines auth, setup, and system info - Remove noisy warning from `find_repository_root`
1 parent d2069d5 commit f84a7b0

16 files changed

Lines changed: 441 additions & 57 deletions

File tree

src/api_client.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,29 @@ nest! {
243243
}
244244
}
245245

246+
nest! {
247+
#[derive(Debug, Deserialize, Serialize)]*
248+
#[serde(rename_all = "camelCase")]*
249+
struct CurrentUserData {
250+
user: Option<pub struct CurrentUserPayload {
251+
pub id: String,
252+
}>,
253+
}
254+
}
255+
246256
impl CodSpeedAPIClient {
257+
/// Check if the current token is valid by querying the user resolver.
258+
pub async fn is_token_valid(&self) -> bool {
259+
let response = self
260+
.gql_client
261+
.query_unwrap::<CurrentUserData>(include_str!("queries/CurrentUser.gql"))
262+
.await;
263+
match response {
264+
Ok(data) => data.user.is_some(),
265+
Err(_) => false,
266+
}
267+
}
268+
247269
pub async fn create_login_session(&self) -> Result<CreateLoginSessionPayload> {
248270
let response = self
249271
.unauthenticated_gql_client

src/cli/auth.rs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
use std::time::Duration;
22

3-
use crate::{api_client::CodSpeedAPIClient, config::CodSpeedConfig, prelude::*};
3+
use crate::api_client::{CodSpeedAPIClient, GetRepositoryVars};
4+
use crate::cli::run::helpers::{
5+
ParsedRepository, find_repository_root, parse_repository_from_remote,
6+
};
7+
use crate::config::CodSpeedConfig;
8+
use crate::prelude::*;
49
use clap::{Args, Subcommand};
510
use console::style;
11+
use git2::Repository;
612
use tokio::time::{Instant, sleep};
713

14+
use super::status::{check_mark, cross_mark};
15+
816
#[derive(Debug, Args)]
917
pub struct AuthArgs {
1018
#[command(subcommand)]
@@ -15,6 +23,8 @@ pub struct AuthArgs {
1523
enum AuthCommands {
1624
/// Login to CodSpeed
1725
Login,
26+
/// Show the authentication status
27+
Status,
1828
}
1929

2030
pub async fn run(
@@ -24,6 +34,7 @@ pub async fn run(
2434
) -> Result<()> {
2535
match args.command {
2636
AuthCommands::Login => login(api_client, config_name).await?,
37+
AuthCommands::Status => status(api_client).await?,
2738
}
2839
Ok(())
2940
}
@@ -80,3 +91,92 @@ async fn login(api_client: &CodSpeedAPIClient, config_name: Option<&str>) -> Res
8091

8192
Ok(())
8293
}
94+
95+
/// Detect the repository from the git remote of the current directory
96+
fn detect_repository() -> Option<ParsedRepository> {
97+
let current_dir = std::env::current_dir().ok()?;
98+
let root_path = find_repository_root(&current_dir)?;
99+
let git_repository = Repository::open(&root_path).ok()?;
100+
let remote = git_repository.find_remote("origin").ok()?;
101+
let url = remote.url()?;
102+
parse_repository_from_remote(url).ok()
103+
}
104+
105+
fn provider_label(provider: &crate::run_environment::RepositoryProvider) -> &'static str {
106+
match provider {
107+
crate::run_environment::RepositoryProvider::GitHub => "GitHub",
108+
crate::run_environment::RepositoryProvider::GitLab => "GitLab",
109+
crate::run_environment::RepositoryProvider::Project => "Project",
110+
}
111+
}
112+
113+
pub async fn status(api_client: &CodSpeedAPIClient) -> Result<()> {
114+
let config = CodSpeedConfig::load_with_override(None, None)?;
115+
let has_token = config.auth.token.is_some();
116+
let detected_repo = detect_repository();
117+
118+
// 1. Check token validity
119+
let token_valid = has_token && api_client.is_token_valid().await;
120+
121+
info!("{}", style("Authentication").bold());
122+
if token_valid {
123+
info!(" {} Logged in", check_mark());
124+
} else if has_token {
125+
info!(
126+
" {} Token expired (run {} to re-authenticate)",
127+
cross_mark(),
128+
style("codspeed auth login").cyan()
129+
);
130+
} else {
131+
info!(
132+
" {} Not logged in (run {} to authenticate)",
133+
cross_mark(),
134+
style("codspeed auth login").cyan()
135+
);
136+
}
137+
info!("");
138+
139+
// 2. If token is valid and we detected a repo, check repository existence
140+
info!("{}", style("Repository").bold());
141+
match detected_repo {
142+
Some(parsed) => {
143+
let label = provider_label(&parsed.provider);
144+
if token_valid {
145+
let repo_exists = api_client
146+
.get_repository(GetRepositoryVars {
147+
owner: parsed.owner.clone(),
148+
name: parsed.name.clone(),
149+
provider: parsed.provider.clone(),
150+
})
151+
.await
152+
.ok()
153+
.flatten()
154+
.is_some();
155+
if repo_exists {
156+
info!(
157+
" {} {}/{} ({})",
158+
check_mark(),
159+
parsed.owner,
160+
parsed.name,
161+
label
162+
);
163+
} else {
164+
info!(
165+
" {} {}/{} ({}, not found on CodSpeed)",
166+
cross_mark(),
167+
parsed.owner,
168+
parsed.name,
169+
label
170+
);
171+
}
172+
} else {
173+
info!(" {}/{} ({})", parsed.owner, parsed.name, label);
174+
}
175+
}
176+
None => {
177+
info!(" Not inside a git repository");
178+
}
179+
}
180+
181+
Ok(())
182+
}

src/cli/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub(crate) mod run;
44
mod setup;
55
mod shared;
66
mod show;
7+
mod status;
78
mod update;
89
mod use_mode;
910

@@ -85,7 +86,9 @@ enum Commands {
8586
/// Manage the CLI authentication state
8687
Auth(auth::AuthArgs),
8788
/// Pre-install the codspeed executors
88-
Setup,
89+
Setup(setup::SetupArgs),
90+
/// Show the overall status of CodSpeed (authentication, tools, system)
91+
Status,
8992
/// Set the codspeed mode for the rest of the shell session
9093
Use(use_mode::UseArgs),
9194
/// Show the codspeed mode previously set in this shell session with `codspeed use`
@@ -142,7 +145,8 @@ pub async fn run() -> Result<()> {
142145
.await?
143146
}
144147
Commands::Auth(args) => auth::run(args, &api_client, cli.config_name.as_deref()).await?,
145-
Commands::Setup => setup::setup(setup_cache_dir).await?,
148+
Commands::Setup(args) => setup::run(args, setup_cache_dir).await?,
149+
Commands::Status => status::run(&api_client).await?,
146150
Commands::Use(args) => use_mode::run(args)?,
147151
Commands::Show => show::run()?,
148152
Commands::Update => update::run().await?,
@@ -160,7 +164,7 @@ impl Cli {
160164
config_name: None,
161165
config: None,
162166
setup_cache_dir: None,
163-
command: Commands::Setup,
167+
command: Commands::Setup(setup::SetupArgs::default()),
164168
}
165169
}
166170
}

src/cli/run/helpers/find_repository_root.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ fn _find_repository_root(base_dir: &Path) -> Option<PathBuf> {
2525
}
2626
}
2727

28-
log::warn!("Could not find repository root");
29-
3028
None
3129
}
3230

src/cli/setup.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
1-
use crate::executor::get_all_executors;
1+
use crate::executor::{ToolInstallStatus, get_all_executors};
22
use crate::prelude::*;
33
use crate::system::SystemInfo;
4+
use clap::{Args, Subcommand};
5+
use console::style;
46
use std::path::Path;
57

6-
pub async fn setup(setup_cache_dir: Option<&Path>) -> Result<()> {
8+
use super::status::{check_mark, cross_mark, warn_mark};
9+
10+
#[derive(Debug, Default, Args)]
11+
pub struct SetupArgs {
12+
#[command(subcommand)]
13+
command: Option<SetupCommands>,
14+
}
15+
16+
#[derive(Debug, Subcommand)]
17+
enum SetupCommands {
18+
/// Show the installation status of CodSpeed tools
19+
Status,
20+
}
21+
22+
pub async fn run(args: SetupArgs, setup_cache_dir: Option<&Path>) -> Result<()> {
23+
match args.command {
24+
None => setup(setup_cache_dir).await,
25+
Some(SetupCommands::Status) => {
26+
status();
27+
Ok(())
28+
}
29+
}
30+
}
31+
32+
async fn setup(setup_cache_dir: Option<&Path>) -> Result<()> {
733
let system_info = SystemInfo::new()?;
834
let executors = get_all_executors();
935
start_group!("Setting up the environment for all executors");
@@ -18,3 +44,31 @@ pub async fn setup(setup_cache_dir: Option<&Path>) -> Result<()> {
1844
end_group!();
1945
Ok(())
2046
}
47+
48+
pub fn status() {
49+
info!("{}", style("Tools").bold());
50+
for executor in get_all_executors() {
51+
let tool_status = executor.tool_status();
52+
match &tool_status.status {
53+
ToolInstallStatus::Installed { version } => {
54+
info!(" {} {} ({})", check_mark(), tool_status.tool_name, version);
55+
}
56+
ToolInstallStatus::IncorrectVersion { version, message } => {
57+
info!(
58+
" {} {} ({}, {})",
59+
warn_mark(),
60+
tool_status.tool_name,
61+
version,
62+
message
63+
);
64+
}
65+
ToolInstallStatus::NotInstalled => {
66+
info!(
67+
" {} {} (not installed)",
68+
cross_mark(),
69+
tool_status.tool_name
70+
);
71+
}
72+
}
73+
}
74+
}

src/cli/status.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use crate::VERSION;
2+
use crate::api_client::CodSpeedAPIClient;
3+
use crate::prelude::*;
4+
use crate::system::SystemInfo;
5+
use console::style;
6+
7+
pub fn check_mark() -> console::StyledObject<&'static str> {
8+
style("✓").green()
9+
}
10+
11+
pub fn cross_mark() -> console::StyledObject<&'static str> {
12+
style("✗").red()
13+
}
14+
15+
pub fn warn_mark() -> console::StyledObject<&'static str> {
16+
style("!").yellow()
17+
}
18+
19+
pub async fn run(api_client: &CodSpeedAPIClient) -> Result<()> {
20+
// Auth status
21+
super::auth::status(api_client).await?;
22+
info!("");
23+
24+
// Setup/tools status
25+
super::setup::status();
26+
info!("");
27+
28+
// System info
29+
info!("{}", style("System").bold());
30+
info!(" codspeed {VERSION}");
31+
let system_info = SystemInfo::new()?;
32+
info!(
33+
" {} {} ({})",
34+
system_info.os, system_info.os_version, system_info.arch
35+
);
36+
info!(
37+
" {} ({}C / {}GB)",
38+
system_info.cpu_brand, system_info.cpu_cores, system_info.total_memory_gb
39+
);
40+
41+
Ok(())
42+
}

src/executor/memory/executor.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::binary_installer::ensure_binary_installed;
21
use crate::executor::ExecutorName;
2+
use crate::executor::ToolStatus;
33
use crate::executor::helpers::command::CommandBuilder;
44
use crate::executor::helpers::env::get_base_injected_env;
55
use crate::executor::helpers::get_bench_command::get_bench_command;
@@ -26,8 +26,7 @@ use std::rc::Rc;
2626
use tempfile::NamedTempFile;
2727
use tokio::time::{Duration, timeout};
2828

29-
const MEMTRACK_COMMAND: &str = "codspeed-memtrack";
30-
const MEMTRACK_CODSPEED_VERSION: &str = "1.2.3";
29+
use super::setup::{MEMTRACK_COMMAND, get_memtrack_status, install_memtrack};
3130

3231
pub struct MemoryExecutor;
3332

@@ -73,23 +72,16 @@ impl Executor for MemoryExecutor {
7372
ExecutorName::Memory
7473
}
7574

75+
fn tool_status(&self) -> ToolStatus {
76+
get_memtrack_status()
77+
}
78+
7679
async fn setup(
7780
&self,
7881
_system_info: &SystemInfo,
7982
_setup_cache_dir: Option<&Path>,
8083
) -> Result<()> {
81-
let get_memtrack_installer_url = || {
82-
format!(
83-
"https://github.com/CodSpeedHQ/codspeed/releases/download/memtrack-v{MEMTRACK_CODSPEED_VERSION}/memtrack-installer.sh"
84-
)
85-
};
86-
87-
ensure_binary_installed(
88-
MEMTRACK_COMMAND,
89-
MEMTRACK_CODSPEED_VERSION,
90-
get_memtrack_installer_url,
91-
)
92-
.await
84+
install_memtrack().await
9385
}
9486

9587
async fn run(

src/executor/memory/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod executor;
2+
pub(crate) mod setup;

0 commit comments

Comments
 (0)