mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-12 03:51:01 +00:00
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>
258 lines
7.8 KiB
Rust
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}");
|
|
}
|
|
}
|
|
}
|