fabro/lib/crates/fabro-cli/src/init.rs
Bryan Helmkamp bba11a9eb9 Rename arc/ to fabro/ in git branch prefixes and workflow paths
Complete the rebrand by replacing hardcoded "arc/run/" branch prefixes
with a RUN_BRANCH_PREFIX constant ("fabro/run/") and updating
fabro init to create workflows under fabro/workflows/ instead of
arc/workflows/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:20:07 -04:00

258 lines
7.8 KiB
Rust

use anyhow::{bail, Context, Result};
use std::path::PathBuf;
pub async fn run_init() -> Result<()> {
let output = std::process::Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()
.context("failed to run git")?;
if !output.status.success() {
bail!("not a git repository — run `git init` first");
}
let repo_root = PathBuf::from(
String::from_utf8(output.stdout)
.context("git output was not valid UTF-8")?
.trim(),
);
let fabro_toml = repo_root.join("fabro.toml");
if fabro_toml.exists() {
bail!(
"already initialized — fabro.toml exists at {}",
fabro_toml.display()
);
}
// Create fabro.toml
std::fs::write(
&fabro_toml,
"\
# Fabro project configuration
# https://docs.fabro.computer/getting-started/quick-start
version = 1
[fabro]
root = \"fabro/\"
# Disable retrospective analysis after workflow runs:
# retro = false
# Auto-create pull requests on successful workflow runs:
# [pull_request]
# enabled = true
# draft = true
",
)
.with_context(|| format!("failed to write {}", fabro_toml.display()))?;
let green = console::Style::new().green();
let bold = console::Style::new().bold();
let dim = console::Style::new().dim();
eprintln!(" {} {}", green.apply_to("✔"), dim.apply_to("fabro.toml"));
// Create hello workflow directory
let workflow_dir = repo_root.join("fabro/workflows/hello");
std::fs::create_dir_all(&workflow_dir)
.with_context(|| format!("failed to create {}", workflow_dir.display()))?;
// Create workflow.fabro
let dot_path = workflow_dir.join("workflow.fabro");
std::fs::write(
&dot_path,
r#"digraph Hello {
graph [goal="Say hello and demonstrate a basic Fabro workflow"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
greet [label="Greet", prompt="Say hello! Introduce yourself and explain that this is a test of the Fabro workflow engine."]
start -> greet -> exit
}
"#,
)
.with_context(|| format!("failed to write {}", dot_path.display()))?;
eprintln!(
" {} {}",
green.apply_to("✔"),
dim.apply_to("fabro/workflows/hello/workflow.fabro")
);
// Create workflow.toml
let toml_path = workflow_dir.join("workflow.toml");
std::fs::write(
&toml_path,
"version = 1\ngraph = \"workflow.fabro\"\n\n[sandbox]\nprovider = \"local\"\n",
)
.with_context(|| format!("failed to write {}", toml_path.display()))?;
eprintln!(
" {} {}",
green.apply_to("✔"),
dim.apply_to("fabro/workflows/hello/workflow.toml")
);
eprintln!(
"\n{} Run a workflow with:\n\n {}",
bold.apply_to("Project initialized!"),
console::Style::new()
.cyan()
.bold()
.apply_to("fabro run hello")
);
check_github_app_installation().await;
Ok(())
}
async fn check_github_app_installation() {
// Get the git remote origin URL
let output = match std::process::Command::new("git")
.args(["remote", "get-url", "origin"])
.output()
{
Ok(o) if o.status.success() => o,
_ => {
let yellow = console::Style::new().yellow();
let dim = console::Style::new().dim();
eprintln!(
"\n {} No git remote found — skipping GitHub App check",
yellow.apply_to("!")
);
eprintln!(
" {}",
dim.apply_to(
"Run `git remote add origin <url>` then `fabro install` to set up the GitHub App"
)
);
return;
}
};
let remote_url = match String::from_utf8(output.stdout) {
Ok(s) => s.trim().to_string(),
Err(_) => return,
};
// Convert SSH URL to HTTPS and parse owner/repo
let https_url = fabro_github::ssh_url_to_https(&remote_url);
let (owner, repo) = match fabro_github::parse_github_owner_repo(&https_url) {
Ok(pair) => pair,
Err(_) => return, // Not a GitHub repo — skip silently
};
// Load CLI config to get app_id and slug
let cli_config = match crate::cli_config::load_cli_config(None) {
Ok(c) => c,
Err(_) => return,
};
let app_id = match cli_config.app_id() {
Some(id) => id.to_string(),
None => {
eprintln!(
"\n Run {} to set up the GitHub App",
console::Style::new()
.cyan()
.bold()
.apply_to("fabro install")
);
return;
}
};
let slug = cli_config.slug().map(String::from);
// Build GitHub App credentials
let creds = match crate::build_github_app_credentials(Some(&app_id)) {
Some(c) => c,
None => {
eprintln!(
"\n Set {} to enable GitHub App integration",
console::Style::new()
.cyan()
.bold()
.apply_to("GITHUB_APP_PRIVATE_KEY")
);
return;
}
};
let jwt = match fabro_github::sign_app_jwt(&creds.app_id, &creds.private_key_pem) {
Ok(j) => j,
Err(e) => {
eprintln!("\n Warning: failed to sign GitHub App JWT: {e}");
return;
}
};
let client = reqwest::Client::new();
match fabro_github::check_app_installed(&client, &jwt, &owner, &repo, "https://api.github.com")
.await
{
Ok(true) => {
let green = console::Style::new().green();
eprintln!(
"\n {} GitHub App is installed for {owner}/{repo}",
green.apply_to("✔")
);
}
Ok(false) => {
let install_url = match &slug {
Some(s) => format!("https://github.com/apps/{s}/installations/new"),
None => format!("https://github.com/organizations/{owner}/settings/installations"),
};
let yellow = console::Style::new().yellow();
eprintln!(
"\n {} GitHub App is not installed for {owner}/{repo}",
yellow.apply_to("!")
);
eprintln!(" Install at: {install_url}");
// Only prompt if stdin is a terminal
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
eprintln!(" Press Enter to continue after installing...");
let _ = tokio::task::spawn_blocking(|| {
let mut buf = String::new();
let _ = std::io::stdin().read_line(&mut buf);
})
.await;
// Re-check after user presses Enter
match fabro_github::check_app_installed(
&client,
&jwt,
&owner,
&repo,
"https://api.github.com",
)
.await
{
Ok(true) => {
let green = console::Style::new().green();
eprintln!(
" {} GitHub App is installed for {owner}/{repo}",
green.apply_to("✔")
);
}
Ok(false) => {
eprintln!(" GitHub App is still not installed.");
eprintln!(" Install at: {install_url}");
}
Err(e) => {
eprintln!(" Warning: could not re-check GitHub App installation: {e}");
}
}
}
}
Err(e) => {
eprintln!("\n Warning: could not check GitHub App installation: {e}");
}
}
}