Skip to content

Commit 571ab68

Browse files
NathanFlurryMasterPtato
authored andcommitted
feat: add non-interactive mode and route management options to CLI
1 parent 9a559a0 commit 571ab68

File tree

8 files changed

+200
-57
lines changed

8 files changed

+200
-57
lines changed

packages/toolchain/cli/src/commands/actor/create.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ impl Opts {
200200
filter_tags: None,
201201
build_tags: Some(build_tags),
202202
version: self.version.clone(),
203-
auto_create_routes: None,
204-
auto_sync_routes: None,
203+
skip_route_creation: None,
204+
keep_existing_routes: None,
205205
non_interactive: false,
206206
})
207207
.await?;

packages/toolchain/cli/src/commands/deploy.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ pub struct Opts {
2020
/// Override the automatically generated version name
2121
#[clap(long)]
2222
version: Option<String>,
23-
24-
/// Automatically create routes for functions (non-interactive mode)
23+
2524
#[clap(long)]
26-
auto_create_routes: Option<bool>,
27-
28-
/// Automatically sync existing routes with configuration (non-interactive mode)
25+
skip_route_creation: Option<bool>,
26+
2927
#[clap(long)]
30-
auto_sync_routes: Option<bool>,
31-
28+
keep_existing_routes: Option<bool>,
29+
3230
/// Run in non-interactive mode (no prompts)
3331
#[clap(long)]
3432
non_interactive: bool,
@@ -60,8 +58,8 @@ impl Opts {
6058
filter_tags: filter_tags,
6159
build_tags: build_tags,
6260
version: self.version.clone(),
63-
auto_create_routes: self.auto_create_routes,
64-
auto_sync_routes: self.auto_sync_routes,
61+
skip_route_creation: self.skip_route_creation,
62+
keep_existing_routes: self.keep_existing_routes,
6563
non_interactive: self.non_interactive,
6664
})
6765
.await?;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use anyhow::*;
2+
use clap::Parser;
3+
use toolchain::{
4+
rivet_api::{apis, models},
5+
ToolchainCtx,
6+
};
7+
8+
/// Get information about a route endpoint
9+
#[derive(Parser, Clone)]
10+
pub struct Opts {
11+
/// Name of the route to retrieve information about
12+
function_name: String,
13+
14+
/// Specify the environment to get the route from (will prompt if not specified)
15+
#[clap(long, alias = "env", short = 'e')]
16+
environment: Option<String>,
17+
}
18+
19+
impl Opts {
20+
pub async fn execute(&self) -> Result<()> {
21+
let ctx = crate::util::login::load_or_login().await?;
22+
let env = crate::util::env::get_or_select(&ctx, self.environment.as_ref()).await?;
23+
24+
// Get route information
25+
let route = get_route(&ctx, &env, &self.function_name).await?;
26+
27+
match route {
28+
Some(route) => {
29+
println!("https://{}{}", route.hostname, route.path);
30+
31+
Ok(())
32+
}
33+
None => Err(anyhow!(
34+
"Route '{}' not found in environment '{}'",
35+
self.function_name,
36+
env
37+
)),
38+
}
39+
}
40+
}
41+
42+
// Helper function to get route if it exists
43+
async fn get_route(
44+
ctx: &ToolchainCtx,
45+
env: &str,
46+
route_id: &str,
47+
) -> Result<Option<models::RoutesRoute>> {
48+
let routes_response = apis::routes_api::routes_list(
49+
&ctx.openapi_config_cloud,
50+
Some(&ctx.project.name_id.to_string()),
51+
Some(env),
52+
)
53+
.await?;
54+
55+
// Find route that matches the ID
56+
let matching_route = routes_response
57+
.routes
58+
.iter()
59+
.find(|route| route.id == *route_id)
60+
.cloned();
61+
62+
Ok(matching_route)
63+
}
64+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use anyhow::*;
2+
use clap::Parser;
3+
use toolchain::rivet_api::apis;
4+
5+
/// List all functions for an environment
6+
#[derive(Parser)]
7+
pub struct Opts {
8+
/// Specify the environment to list function for (will prompt if not specified)
9+
#[clap(long, alias = "env", short = 'e')]
10+
environment: Option<String>,
11+
}
12+
13+
impl Opts {
14+
pub async fn execute(&self) -> Result<()> {
15+
let ctx = crate::util::login::load_or_login().await?;
16+
let env = crate::util::env::get_or_select(&ctx, self.environment.as_ref()).await?;
17+
18+
// Get routes
19+
let routes_response = apis::routes_api::routes_list(
20+
&ctx.openapi_config_cloud,
21+
Some(&ctx.project.name_id.to_string()),
22+
Some(&env),
23+
)
24+
.await?;
25+
26+
if routes_response.routes.is_empty() {
27+
println!("No routes found for environment '{}'", env);
28+
return Ok(());
29+
}
30+
31+
for route in routes_response.routes {
32+
println!("- {}: {}{}", route.id, route.hostname, route.path);
33+
}
34+
35+
Ok(())
36+
}
37+
}
38+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use anyhow::*;
2+
use clap::Parser;
3+
4+
pub mod endpoint;
5+
pub mod list;
6+
7+
/// Commands for managing routes
8+
#[derive(Parser)]
9+
pub enum SubCommand {
10+
/// List all routes
11+
List(list::Opts),
12+
/// Get information about a specific route endpoint
13+
Endpoint(endpoint::Opts),
14+
}
15+
16+
impl SubCommand {
17+
pub async fn execute(&self) -> Result<()> {
18+
match self {
19+
SubCommand::List(opts) => opts.execute().await,
20+
SubCommand::Endpoint(opts) => opts.execute().await,
21+
}
22+
}
23+
}

packages/toolchain/cli/src/commands/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod config;
44
pub mod deno;
55
pub mod deploy;
66
pub mod environment;
7+
pub mod function;
78
pub mod login;
89
pub mod logout;
910
pub mod metadata;
@@ -60,11 +61,13 @@ pub enum SubCommand {
6061
subcommand: region::SubCommand,
6162
},
6263
/// Commands for managing routes
63-
#[clap(alias = "r", alias = "endpoint")]
64-
Route {
64+
#[clap(alias = "f", alias = "func", alias = "route")]
65+
Function {
6566
#[clap(subcommand)]
66-
subcommand: route::SubCommand,
67+
subcommand: function::SubCommand,
6768
},
69+
/// Get information about a specific route endpoint
70+
Endpoint(function::endpoint::Opts),
6871
/// Commands for managing Rivet configuration
6972
Config {
7073
#[clap(subcommand)]
@@ -102,7 +105,8 @@ impl SubCommand {
102105
SubCommand::Actor { subcommand } => subcommand.execute().await,
103106
SubCommand::Build { subcommand } => subcommand.execute().await,
104107
SubCommand::Region { subcommand } => subcommand.execute().await,
105-
SubCommand::Route { subcommand } => subcommand.execute().await,
108+
SubCommand::Function { subcommand } => subcommand.execute().await,
109+
SubCommand::Endpoint(opts) => opts.execute().await,
106110
SubCommand::Config { subcommand } => subcommand.execute().await,
107111
SubCommand::Metadata { subcommand } => subcommand.execute().await,
108112
SubCommand::Deno(opts) => opts.execute().await,

packages/toolchain/cli/src/util/deploy.rs

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ pub struct DeployOpts<'a> {
1919
pub filter_tags: Option<HashMap<String, String>>,
2020
pub build_tags: Option<HashMap<String, String>>,
2121
pub version: Option<String>,
22-
pub auto_create_routes: Option<bool>,
23-
pub auto_sync_routes: Option<bool>,
22+
pub skip_route_creation: Option<bool>,
23+
pub keep_existing_routes: Option<bool>,
2424
pub non_interactive: bool,
2525
}
2626

@@ -69,14 +69,15 @@ pub async fn deploy(opts: DeployOpts<'_>) -> Result<Vec<Uuid>> {
6969

7070
// Setup function routes
7171
setup_function_routes(
72-
opts.ctx,
73-
environment,
74-
&config,
72+
opts.ctx,
73+
environment,
74+
&config,
7575
&opts.filter_tags,
76-
opts.auto_create_routes,
77-
opts.auto_sync_routes,
78-
opts.non_interactive
79-
).await?;
76+
opts.skip_route_creation,
77+
opts.keep_existing_routes,
78+
opts.non_interactive,
79+
)
80+
.await?;
8081

8182
// Print summary
8283
print_summary(opts.ctx, environment);
@@ -89,23 +90,23 @@ async fn setup_function_routes(
8990
environment: &toolchain::project::environment::TEMPEnvironment,
9091
config: &config::Config,
9192
filter_tags: &Option<HashMap<String, String>>,
92-
auto_create_routes: Option<bool>,
93-
auto_sync_routes: Option<bool>,
93+
skip_route_creation: Option<bool>,
94+
keep_existing_routes: Option<bool>,
9495
non_interactive: bool,
9596
) -> Result<()> {
96-
// Determine default hostname based on project & env
97-
let default_hostname = format!(
98-
"{}-{}.{}",
99-
ctx.project.name_id,
100-
environment.slug,
101-
ctx.bootstrap
102-
.domains
103-
.job
104-
.as_ref()
105-
.context("bootstrap.domains.job")?
106-
);
107-
10897
for (fn_name, function) in &config.functions {
98+
// Determine default hostname based on project & env
99+
let default_hostname = format!(
100+
"{}-{}-{fn_name}.{}",
101+
ctx.project.name_id,
102+
environment.slug,
103+
ctx.bootstrap
104+
.domains
105+
.job
106+
.as_ref()
107+
.context("bootstrap.domains.job")?
108+
);
109+
109110
// TODO: Convert this in to a shared fn
110111
// Filter out builds that match the tags
111112
if let Some(filter) = &filter_tags {
@@ -204,20 +205,22 @@ async fn setup_function_routes(
204205
];
205206

206207
println!();
207-
208+
208209
let choice_index = if non_interactive {
209210
// In non-interactive mode, use auto_sync_routes if provided, otherwise sync by default
210-
if let Some(auto_sync) = auto_sync_routes {
211-
if auto_sync {
212-
println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)");
213-
0 // Sync route with config
214-
} else {
211+
match keep_existing_routes {
212+
Some(true) => {
215213
println!("Skipping route sync for '{fn_name}' (non-interactive mode)");
216214
1 // Keep existing route
217215
}
218-
} else {
219-
println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)");
220-
0 // Default to sync in non-interactive mode
216+
Some(false) => {
217+
println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)");
218+
0 // Sync route with config
219+
}
220+
None => {
221+
println!("Auto-syncing route configuration for '{fn_name}' (non-interactive mode)");
222+
0 // Default to sync in non-interactive mode
223+
}
221224
}
222225
} else {
223226
// Interactive mode - prompt the user
@@ -304,17 +307,23 @@ async fn setup_function_routes(
304307

305308
let choice_index = if non_interactive {
306309
// In non-interactive mode, use auto_create_routes if provided, otherwise create by default
307-
if let Some(auto_create) = auto_create_routes {
308-
if auto_create {
309-
println!("Auto-creating route for function '{fn_name}' (non-interactive mode)");
310-
0 // Create default route
311-
} else {
310+
match skip_route_creation {
311+
Some(true) => {
312312
println!("Skipping route creation for '{fn_name}' (non-interactive mode)");
313313
1 // Skip route creation
314314
}
315-
} else {
316-
println!("Auto-creating route for function '{fn_name}' (non-interactive mode)");
317-
0 // Default to create in non-interactive mode
315+
Some(false) => {
316+
println!(
317+
"Auto-creating route for function '{fn_name}' (non-interactive mode)"
318+
);
319+
0 // Create default route
320+
}
321+
None => {
322+
println!(
323+
"Auto-creating route for function '{fn_name}' (non-interactive mode)"
324+
);
325+
0 // Default to create in non-interactive mode
326+
}
318327
}
319328
} else {
320329
// Interactive mode - prompt the user
@@ -436,4 +445,5 @@ async fn create_function_route(
436445
}
437446

438447
Ok(())
439-
}
448+
}
449+

packages/toolchain/toolchain/src/toolchain_ctx.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ pub fn cloud_config_from_env() -> Option<(String, String)> {
5050

5151
/// If the credentials already exist or loading credentials from env.
5252
pub async fn has_cloud_config() -> Result<bool> {
53-
if cloud_config_from_env().is_some() {
53+
if let Some((api_endpoint, token)) = cloud_config_from_env() {
54+
meta::mutate_project(&paths::data_dir()?, |meta| {
55+
if meta.cloud.is_none() {
56+
meta.cloud = Some(meta::Cloud::new(api_endpoint, token))
57+
}
58+
})
59+
.await?;
5460
Ok(true)
5561
} else {
5662
meta::read_project(&paths::data_dir()?, |x| x.cloud.is_some()).await

0 commit comments

Comments
 (0)