pub(crate) mod cache;
pub mod change;
pub mod clean;
#[doc(hidden)]
pub mod cli;
pub mod common;
pub mod config;
pub mod doctor;
pub mod init;
pub mod plan;
pub mod release;
pub mod split;
pub mod surface;
pub mod sync;
pub mod unify;
pub use change::{ChangeCheckOptions, run_change_add, run_change_check, run_change_status};
pub use clean::{CleanContext, run_clean};
#[doc(hidden)]
pub use cli::{
CacheCommand, CacheScope, ChangeCommand, Commands, DoctorCommand, RailCli, ReleaseCommand, SplitCommand,
generate_completions,
};
pub use common::{ChangeOutputFormat, SplitOutputFormat, SurfaceOutputFormat, TextJsonOutputFormat};
pub use config::{
StrictnessMode, run_config_explain, run_config_locate, run_config_print, run_config_validate_standalone,
};
pub use doctor::run_native_cache_doctor;
pub use init::{run_init, run_init_standalone};
pub use plan::{PlanOptions, run_plan};
pub use release::{
run_release_init, run_release_plan, run_release_publication_check, run_release_publish,
run_release_status_standalone,
};
pub use split::{run_split, run_split_init};
pub use surface::{SurfaceOptions, run_surface};
pub use sync::run_sync;
pub use unify::{UnifyAnalyzeOptions, run_unify_analyze, run_unify_apply, run_unify_doctor, run_unify_undo};
use crate::error::RailResult;
use crate::workspace::WorkspaceContext;
use std::path::{Path, PathBuf};
#[derive(Debug)]
#[doc(hidden)]
pub enum PreContextDispatch {
Handled,
NeedsContext(PreparedContext),
}
#[derive(Debug)]
#[doc(hidden)]
pub struct PreparedContext {
command: Box<Commands>,
config_override: Option<PathBuf>,
plan_options: Option<PlanOptions>,
}
impl PreparedContext {
fn new(command: Commands, config_override: Option<&Path>) -> RailResult<Self> {
Ok(Self {
command: Box::new(command),
config_override: config_override.map(Path::to_path_buf),
plan_options: None,
})
}
fn new_plan(command: Commands, config_override: Option<&Path>, plan_options: PlanOptions) -> Self {
Self {
command: Box::new(command),
config_override: config_override.map(Path::to_path_buf),
plan_options: Some(plan_options),
}
}
#[doc(hidden)]
pub fn build(mut self, workspace_root: &Path) -> RailResult<(Commands, WorkspaceContext, Option<PlanOptions>)> {
let historical = self
.plan_options
.as_ref()
.map(|options| options.comparison.resolve_objects_before_context(workspace_root))
.transpose()?
.flatten();
let context = if let Some((from, to)) = historical {
let context = WorkspaceContext::build_historical_planning_with_config(
workspace_root,
&from,
&to,
self.config_override.as_deref(),
);
if let Some(options) = self.plan_options.as_mut() {
options.comparison.replace_objects(from, to);
}
context
} else if self.command.requires_workspace_snapshot() {
WorkspaceContext::build_with_snapshot_and_config(workspace_root, self.config_override.as_deref())
} else if self.command.requires_planning_source_capture() {
WorkspaceContext::build_with_planning_capture_and_config(workspace_root, self.config_override.as_deref())
} else {
WorkspaceContext::build_with_source_capture_and_config(
workspace_root,
self.command.requires_worktree_source_capture(),
self.config_override.as_deref(),
)
};
Ok((*self.command, context?, self.plan_options))
}
}
#[doc(hidden)]
pub fn try_dispatch_pre_context(
cmd: Commands,
workspace_root: &Path,
config_override: Option<&Path>,
json: bool,
) -> RailResult<PreContextDispatch> {
match cmd {
Commands::Plan {
verify: Some(plan_file),
..
} => {
plan::verify_saved_plan(workspace_root, config_override, &plan_file)?;
Ok(PreContextDispatch::Handled)
}
Commands::Plan { schema: true, .. } => {
plan::print_plan_schema();
Ok(PreContextDispatch::Handled)
}
Commands::Plan {
since,
from,
to,
json,
explain,
explain_work,
all,
evidence,
verify: None,
schema: false,
} => {
let comparison = plan::PlanComparison::from_cli(&since, &from, &to)?;
let command = Commands::Plan {
since,
from,
to,
json,
explain,
explain_work: explain_work.clone(),
all,
evidence: evidence.clone(),
verify: None,
schema: false,
};
Ok(PreContextDispatch::NeedsContext(PreparedContext::new_plan(
command,
config_override,
PlanOptions {
comparison,
json,
explain,
explain_work,
all,
evidence,
},
)))
}
Commands::Surface { schema: true, .. } => {
surface::print_surface_schema();
Ok(PreContextDispatch::Handled)
}
command @ Commands::Surface { .. } => {
crate::compiler::driver::CompilerFactDriverAuthority::require_surface_installation()?;
Ok(PreContextDispatch::NeedsContext(PreparedContext::new(
command,
config_override,
)?))
}
Commands::Init {
output,
force,
dry_run,
targets,
detect_targets,
} => {
init::run_init_standalone(workspace_root, &output, force, dry_run, json, &targets, detect_targets)?;
Ok(PreContextDispatch::Handled)
}
Commands::Unify {
command:
Some(cli::UnifyCommand::Undo {
list,
backup_id,
format,
}),
..
} => {
unify::run_unify_undo(workspace_root, list, backup_id, format)?;
Ok(PreContextDispatch::Handled)
}
Commands::Config { command } => {
match command.unwrap_or(cli::ConfigCommand::Explain {
fields: Vec::new(),
all: false,
format: common::TextJsonOutputFormat::Text,
}) {
cli::ConfigCommand::Validate {
format,
strict,
no_strict,
} => {
let strictness = if strict {
StrictnessMode::Strict
} else if no_strict {
StrictnessMode::NoStrict
} else {
StrictnessMode::Auto
};
config::run_config_validate_standalone(workspace_root, config_override, format, strictness)?;
}
cli::ConfigCommand::Locate { format } => {
config::run_config_locate(workspace_root, config_override, format)?
}
cli::ConfigCommand::Print { format } => {
config::run_config_print(workspace_root, config_override, format)?
}
cli::ConfigCommand::Explain { fields, all, format } => {
config::run_config_explain(workspace_root, config_override, &fields, all, format)?
}
}
Ok(PreContextDispatch::Handled)
}
Commands::Completions { shell } => {
cli::generate_completions(shell);
Ok(PreContextDispatch::Handled)
}
Commands::Clean {
all,
cache,
prune_backups,
all_backups,
reports,
release_journal,
check,
format,
} => {
let context = clean::CleanContext::capture(workspace_root, config_override)?;
run_clean(
&context,
clean::CleanOptions {
all,
cache,
prune_backups,
all_backups,
reports,
release_journal,
check,
format,
},
)?;
Ok(PreContextDispatch::Handled)
}
Commands::Cache { command } => {
match command {
cli::CacheCommand::Setup(setup) => {
let cli::CacheSetupArgs {
profile,
local_dir,
max_size,
remote,
remote_mode,
remote_environment,
root_portability,
local_only,
distributed_local,
distributed_endpoint,
distributed_server_name,
distributed_capability,
distributed_authority,
distributed_client_certificate,
distributed_client_private_key,
distributed_policy,
check,
format,
} = *setup;
cache::run_setup(
workspace_root,
crate::cache::installation::SetupRequest {
profile_id: profile,
local_dir,
max_bytes: max_size,
remote_url: remote,
remote_mode,
remote_environment,
root_portability,
local_only,
distributed_local,
distributed_endpoint,
distributed_server_name,
distributed_capability,
distributed_authority,
distributed_client_certificate,
distributed_client_private_key,
distributed_policy,
},
check,
format,
)?;
}
cli::CacheCommand::Ready { format } => cache::run_ready(workspace_root, format)?,
cli::CacheCommand::Normalize {
url,
mode,
environment,
format,
} => cache::run_normalize(&url, mode.as_deref(), environment, format)?,
cli::CacheCommand::Probe { format } => cache::run_probe(workspace_root, format)?,
cli::CacheCommand::Report { start, finish, format } => {
cache::run_report(start.as_deref(), finish.as_deref(), format)?
}
cli::CacheCommand::Status { scope, format } => cache::run_status(workspace_root, scope, format)?,
cli::CacheCommand::Profiles { format } => cache::run_profiles(workspace_root, format)?,
cli::CacheCommand::Detach { check, format } => {
cache::run_detach(workspace_root, check, format)?;
}
cli::CacheCommand::DropProfile { profile, check, format } => {
cache::run_drop_profile(workspace_root, &profile, check, format)?;
}
cli::CacheCommand::Recover { check, format } => {
cache::run_recover(workspace_root, check, format)?;
}
cli::CacheCommand::Clean { scope, check, format } => {
cache::run_clean(workspace_root, scope, check, format)?;
}
cli::CacheCommand::Uninstall { check, format } => {
cache::run_uninstall(workspace_root, check, format)?;
}
}
Ok(PreContextDispatch::Handled)
}
Commands::Release {
command: cli::ReleaseCommand::Record { command },
} => {
let result = match command {
cli::ReleaseRecordCommand::Inspect { transaction } => {
let path = crate::release::state::resolve_active(workspace_root, Some(&transaction))?;
serde_json::to_value(crate::release::state::ReleaseState::load(&path)?)?
}
cli::ReleaseRecordCommand::Store { transaction } => {
crate::release::transfer::store(workspace_root, &transaction)?
}
cli::ReleaseRecordCommand::Fetch { transaction } => {
crate::release::transfer::fetch(workspace_root, transaction.as_deref())?
}
cli::ReleaseRecordCommand::Export { transaction, directory } => {
crate::release::transfer::export(workspace_root, &transaction, &directory)?
}
cli::ReleaseRecordCommand::Import {
directory,
intent,
source,
} => crate::release::transfer::import(workspace_root, &directory, &intent, &source)?,
};
println!("{}", serde_json::to_string(&result)?);
Ok(PreContextDispatch::Handled)
}
Commands::Release {
command: cli::ReleaseCommand::Status { state, history, format },
} => {
release::run_release_status_standalone(workspace_root, state.as_deref(), history, format)?;
Ok(PreContextDispatch::Handled)
}
Commands::Release {
command: cli::ReleaseCommand::Resume { transaction, executor },
} => {
crate::release::hosted::refresh(workspace_root, transaction.as_deref())?;
let state = crate::release::state::resolve_active(workspace_root, transaction.as_deref())?;
let retained = crate::release::state::ReleaseState::load(&state)?;
if executor {
crate::release::hosted::checkout(workspace_root, &retained)?;
}
if !retained.intent.hosted || executor {
crate::release::state::prepare_recovery(workspace_root, &state)?;
}
Ok(PreContextDispatch::NeedsContext(PreparedContext::new(
Commands::Release {
command: cli::ReleaseCommand::Resume { transaction, executor },
},
config_override,
)?))
}
Commands::Release {
command:
cli::ReleaseCommand::Abort {
transaction,
retain_preparation,
yes,
},
} => {
crate::release::hosted::refresh(workspace_root, transaction.as_deref())?;
let state = crate::release::state::resolve_active(workspace_root, transaction.as_deref())?;
crate::release::state::prepare_recovery(workspace_root, &state)?;
Ok(PreContextDispatch::NeedsContext(PreparedContext::new(
Commands::Release {
command: cli::ReleaseCommand::Abort {
transaction,
retain_preparation,
yes,
},
},
config_override,
)?))
}
other => Ok(PreContextDispatch::NeedsContext(PreparedContext::new(
other,
config_override,
)?)),
}
}
pub fn dispatch(cmd: Commands, ctx: &WorkspaceContext, prepared_plan: Option<PlanOptions>) -> RailResult<()> {
match cmd {
Commands::Doctor {
command: cli::DoctorCommand::NativeCache { format },
} => run_native_cache_doctor(ctx, format),
Commands::Plan { .. } => run_plan(
ctx,
prepared_plan.ok_or_else(|| crate::error::RailError::message("plan comparison was not prepared"))?,
),
Commands::Surface {
prepare,
check,
fix,
resume,
dry_run,
backup,
format,
output,
explain,
only,
schema: _,
} => run_surface(
ctx,
SurfaceOptions {
prepare,
check,
fix,
resume,
dry_run,
backup,
format,
output,
explain,
only,
},
),
Commands::Init { .. } => Err(crate::error::RailError::message(
"init command reached workspace dispatch",
)),
Commands::Unify {
command,
check,
format,
report,
report_path,
output,
show_diff,
explain,
} => match command {
Some(cli::UnifyCommand::Doctor { format }) => run_unify_doctor(ctx, format),
Some(cli::UnifyCommand::Apply {
plan,
backup,
report,
report_path,
format,
}) => run_unify_apply(ctx, backup, !report, report_path, plan, format),
Some(cli::UnifyCommand::Undo { .. }) => Err(crate::error::RailError::message(
"unify undo reached workspace dispatch",
)),
None => {
if !check {
crate::warn!(
"bare 'cargo rail unify' is preview-only; use 'cargo rail unify apply' to modify manifests"
);
}
run_unify_analyze(
ctx,
UnifyAnalyzeOptions {
check,
show_diff,
explain,
format,
output: output.as_ref(),
backup: false,
no_report: !report,
report_path: report_path.as_ref(),
},
)
}
},
Commands::Split { command } => match command {
cli::SplitCommand::Init { crate_names, dry_run } => {
let crates = if crate_names.is_empty() {
None
} else {
Some(crate_names)
};
run_split_init(ctx, crates, dry_run)
}
cli::SplitCommand::Run {
crate_name,
all,
remote,
check,
plan,
allow_dirty,
yes,
format,
} => run_split(
ctx,
split::SplitRunArgs {
crate_name,
all,
remote,
check,
plan_path: plan,
allow_dirty,
yes,
format,
},
),
},
Commands::Sync {
crate_name,
all,
remote,
from_remote,
to_remote,
strategy,
check,
plan,
resume,
allow_dirty,
yes,
format,
} => run_sync(
ctx,
sync::SyncArgs {
crate_name,
all,
remote,
from_remote,
to_remote,
strategy,
check,
plan_path: plan,
resume,
allow_dirty,
yes,
format,
},
),
Commands::Change { command } => match command {
cli::ChangeCommand::Add {
crate_names,
bump,
message,
name,
format,
} => run_change_add(ctx, crate_names, bump, message, name, format),
cli::ChangeCommand::Status { format } => run_change_status(ctx, format),
cli::ChangeCommand::Check {
since,
merge_base,
all,
format,
} => run_change_check(
ctx,
ChangeCheckOptions {
since,
merge_base,
all,
format,
},
),
},
Commands::Release { command } => match command {
cli::ReleaseCommand::Init { crate_names, dry_run } => {
let crates = if crate_names.is_empty() {
None
} else {
Some(crate_names)
};
run_release_init(ctx, crates, dry_run)
}
cli::ReleaseCommand::Run {
crate_names,
all,
bump,
plan,
publish,
skip_tag,
pr,
local,
executor,
prepare,
retain_remote,
include_dependents,
yes,
allow_non_default_branch,
format,
} => {
let names = if all || crate_names.is_empty() {
None
} else {
Some(crate_names)
};
run_release_publish(
ctx,
release::ReleasePublishArgs {
crate_names: names,
all,
bump,
publish,
skip_tag,
pr,
local,
executor,
prepare,
retain_remote,
include_dependents,
yes,
allow_non_default_branch,
plan_path: plan,
format,
},
)
}
cli::ReleaseCommand::Check {
crate_names,
all,
bump,
publication,
extended,
skip_tag,
include_dependents,
format,
} => {
let names = if all || crate_names.is_empty() {
None
} else {
Some(crate_names)
};
if publication {
release::run_release_publication_check_with_plan_inputs(
ctx,
release::ReleasePublicationCheckArgs {
crate_names: names,
all,
bump,
extended,
skip_tag,
include_dependents,
format,
},
)
} else {
run_release_plan(ctx, names, bump, false, skip_tag, include_dependents, format)
}
}
cli::ReleaseCommand::Resume { transaction, executor } => {
release::run_release_resume(ctx, transaction.as_deref(), executor)
}
cli::ReleaseCommand::Status { .. } | cli::ReleaseCommand::Record { .. } => Err(
crate::error::RailError::message("release status reached workspace dispatch"),
),
cli::ReleaseCommand::Abort {
transaction,
retain_preparation,
yes,
} => release::run_release_abort_with_options(ctx, transaction.as_deref(), retain_preparation, yes),
},
Commands::Clean { .. } => Err(crate::error::RailError::message(
"clean command reached workspace dispatch",
)),
Commands::Cache { .. } => Err(crate::error::RailError::message(
"cache command reached workspace dispatch",
)),
Commands::Config { .. } => Err(crate::error::RailError::message(
"config command reached workspace dispatch",
)),
Commands::Completions { .. } => Err(crate::error::RailError::message(
"completions command reached workspace dispatch",
)),
}
}