memstead_cli/commands/
pipeline.rs1use clap::{Args as ClapArgs, Subcommand};
8use serde_json::json;
9
10use crate::CliError;
11use crate::output::{ExitKind, print_json, print_markdown};
12use crate::setup::CliContext;
13
14#[derive(ClapArgs, Debug)]
15pub struct Args {
16 #[command(subcommand)]
17 pub command: PipelineCommand,
18}
19
20#[derive(Subcommand, Debug)]
21pub enum PipelineCommand {
22 Migrate,
28}
29
30pub fn run(ctx: &CliContext, args: Args) -> anyhow::Result<()> {
31 match args.command {
32 PipelineCommand::Migrate => migrate(ctx),
33 }
34}
35
36fn migrate(ctx: &CliContext) -> anyhow::Result<()> {
37 let (_shape, root) = ctx.workspace_shape().ok_or_else(|| {
38 CliError::new(
39 ExitKind::Generic,
40 "NO_WORKSPACE",
41 "not inside a Memstead workspace (no `.memstead/workspace.toml` in any ancestor)"
42 .to_string(),
43 )
44 })?;
45 let configs = memstead_base::migrate_legacy_pipeline(&root).map_err(|e| {
46 CliError::new(
47 ExitKind::Generic,
48 "PIPELINE_MIGRATE_FAILED",
49 format!("pipeline migration failed: {e}"),
50 )
51 .with_details(json!({ "error": e.to_string() }))
52 })?;
53 if ctx.json {
54 print_json(&json!({
55 "ok": true,
56 "mediums": configs.mediums.len(),
57 "facets": configs.facets.len(),
58 "projections": configs.projections.len(),
59 "ingests": configs.ingests.len(),
60 }))?;
61 } else {
62 print_markdown(&format!(
63 "# Pipeline migrated\n\nWrote to `.memstead/`: {} medium(s), {} facet(s), \
64 {} projection(s), {} ingest(s).\n\nThe legacy `scopes|projections|ingests/` \
65 folders were left in place — remove them when ready.\n",
66 configs.mediums.len(),
67 configs.facets.len(),
68 configs.projections.len(),
69 configs.ingests.len(),
70 ));
71 }
72 Ok(())
73}