use std::env;
use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use crate::catalog::{tool_catalog, tool_usage};
use crate::commands::provision;
use crate::config::GlobalConfig;
use crate::steps::{asphalt, notify, toolchain, tungsten};
use crate::ui;
pub fn run(tool: Option<&str>) -> Result<()> {
match tool {
Some(key) => setup_tool(key),
None => setup_machine(),
}
}
fn setup_machine() -> Result<()> {
ui::detail("Nothing already installed is reinstalled. Safe to re-run any time.");
let mut config = GlobalConfig::load()?;
provision::run(&mut config)?;
config.save()?;
notify::summary(
"rproj setup complete",
"Your machine is ready for Roblox development.",
);
println!("\nDone. Run `rproj new <name>` to scaffold your first project.");
Ok(())
}
fn setup_tool(key: &str) -> Result<()> {
let entry = tool_catalog::find(key).with_context(|| {
format!("no tool called `{key}`. `rproj info` lists everything rproj knows about")
})?;
let project_dir = find_project_root()?;
ui::section(&format!(
"Setting up {} in {}",
entry.key,
project_dir.display()
));
match entry.kind {
tool_catalog::ToolKind::RokitTool { .. } => {
toolchain::ensure_rokit_init(&project_dir)?;
toolchain::add_selected_tools(
&project_dir,
std::slice::from_ref(&entry.key.to_string()),
)?;
}
_ => ui::skip(&format!(
"{} is a {}, so there is nothing to pin per project",
entry.key,
entry.kind.label()
)),
}
match entry.key {
"asphalt" => asphalt::ensure_config(&project_dir)?,
"tungsten" => tungsten::ensure_config(&project_dir)?,
_ => {
if tool_usage::find(entry.key).is_some() {
ui::skip(&format!(
"no config file for {} - `rproj configure {}` if it has settings",
entry.key, entry.key
));
}
}
}
if let Some(usage) = tool_usage::find(entry.key) {
if !usage.notes.is_empty() {
println!();
ui::detail("Worth knowing:");
for note in usage.notes {
ui::detail(&format!(" - {note}"));
}
}
if let Some((command, what)) = usage.commands.first() {
println!();
ui::detail("Try next:");
ui::detail(&format!(" {command}"));
ui::detail(&format!(" {what}"));
}
}
Ok(())
}
fn find_project_root() -> Result<PathBuf> {
let start = env::current_dir().context("could not read the current directory")?;
for dir in start.ancestors() {
if dir.join("default.project.json").is_file() {
return Ok(dir.to_path_buf());
}
}
bail!(
"not inside a Roblox project - no default.project.json here or above {}.\n\
`rproj setup` with no tool name sets up the machine instead, and \
`rproj new <name>` creates a project.",
start.display()
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unknown_tool_is_named_in_the_error() {
let err = setup_tool("definitely-not-a-tool").unwrap_err();
let message = format!("{err}");
assert!(message.contains("definitely-not-a-tool"), "{message}");
assert!(message.contains("rproj info"), "{message}");
}
#[test]
fn the_documented_setup_candidates_all_have_usage_text() {
for key in ["asphalt", "tungsten", "lute", "luau-lsp-cli"] {
assert!(
tool_catalog::find(key).is_some(),
"{key} is not in the tool catalog"
);
let usage = tool_usage::find(key).unwrap_or_else(|| {
panic!("{key} has no usage entry, so setup would print nothing")
});
assert!(!usage.commands.is_empty(), "{key} lists no commands");
}
}
}