1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use super::*;
use crate::workspace::{
ProjectWithWorkspace, project_favorites_by_workspace, workspaces_with_client,
};
/// Marks a favorited project in the human listing. Favorites also sort to the
/// top of their workspace, so the glyph is a confirmation of the grouping
/// rather than the only signal — which keeps the listing readable where the
/// star cannot render.
const FAVORITE_MARKER: &str = "★ ";
/// List all projects in your Railway account
#[derive(Parser)]
pub struct Args {
/// Output in JSON format
#[clap(long)]
json: bool,
}
pub async fn command(args: Args) -> Result<()> {
let configs = Configs::new()?;
let linked_project = configs.get_linked_project().await.ok();
let client = GQLClient::new_authorized(&configs)?;
let workspaces = workspaces_with_client(&client, &configs).await?;
// Favorites are fetched per workspace and joined here rather than being
// selected alongside the projects: `projectFavorites` hangs off `Query`,
// not off a workspace. A caller the field refuses just gets empty lists,
// and the listing below renders exactly as it did before favorites.
let favorites = project_favorites_by_workspace(
&client,
&configs,
workspaces.iter().map(|w| w.id().to_string()).collect(),
)
.await;
let mut all_projects: Vec<ProjectWithWorkspace> = Vec::new();
for workspace in workspaces {
let workspace_favorites = favorites
.get(workspace.id())
.map(Vec::as_slice)
.unwrap_or_default();
if !args.json {
println!();
println!("{}", workspace.name().bold());
let ranked = workspace.projects_ranked(workspace_favorites);
// Only reserve the marker's width once this workspace actually has
// a favorite to show. With none — the degraded case for the tokens
// the favorites field refuses, and for an account that has starred
// nothing — every line stays byte-for-byte what it was before
// favorites existed.
let reserve_marker_width = ranked.iter().any(|(_, is_favorite)| *is_favorite);
for (project, is_favorite) in ranked {
let project_name =
if Some(project.id()) == linked_project.as_ref().map(|p| p.project.as_str()) {
project.name().purple().bold()
} else {
project.name().white()
};
if is_favorite {
println!(" {}{project_name}", FAVORITE_MARKER.yellow());
} else if reserve_marker_width {
let pad = " ".repeat(FAVORITE_MARKER.chars().count());
println!(" {pad}{project_name}");
} else {
println!(" {project_name}");
}
}
}
all_projects.extend(workspace.projects_with_workspace(workspace_favorites));
}
if args.json {
println!("{}", serde_json::to_string_pretty(&all_projects)?);
}
Ok(())
}