spec_driven_docs/commands/
stage.rs1use camino::Utf8PathBuf;
8
9use crate::cli::stage::{StageArgs, StageCleanArgs, StageCommand};
10use crate::context::AppContext;
11use crate::domain::paths::UserEnv;
12use crate::error::AppError;
13use crate::output;
14use crate::stage::{self, Request};
15
16pub fn run(ctx: &AppContext, args: StageArgs) -> Result<(), AppError> {
24 if let Some(StageCommand::Clean(clean)) = args.command {
25 return remove(&clean);
26 }
27 let target = resolved(ctx, args.target)?;
28 let state_root = UserEnv::from_process()
29 .state_root()
30 .ok_or_else(|| AppError::Usage("no state root resolves".to_string()))?
31 .path;
32 let docs_scratch = args
33 .docs_scratch
34 .as_deref()
35 .map(crate::domain::manifest::parse_docs_scratch)
36 .transpose()
37 .map_err(|error| AppError::Usage(format!("--docs-scratch: {error}")))?;
38 let writing_style = args
39 .writing_style
40 .as_deref()
41 .map(crate::domain::instance_config::WritingStyle::parse_flag)
42 .transpose()
43 .map_err(|error| AppError::Usage(format!("--writing-style: {error}")))?;
44 let receipt = stage::create(
45 &Request {
46 target,
47 profile: args.profile,
48 output: args.output,
49 docs_scratch,
50 reserve: args.reserve,
51 writing_style,
52 },
53 &state_root,
54 )?;
55
56 if args.json {
57 return output::json(&receipt);
58 }
59 output::line(format!(
60 "staged spec-driven-docs {} ({}) for {}",
61 receipt.version, receipt.profile, receipt.target
62 ));
63 output::line(format!("stage: {}", receipt.root));
64 output::line(format!(
65 "{} artifacts under {}/, reference material under {}/",
66 receipt.artifacts.len(),
67 stage::ARTIFACTS_DIR,
68 stage::REFERENCE_DIR
69 ));
70 for note in &receipt.notes {
71 output::line(note.clone());
72 }
73 output::line(format!(
74 "the stage stays until 'sdd stage clean {}' removes it",
75 receipt.root
76 ));
77 Ok(())
78}
79
80fn remove(args: &StageCleanArgs) -> Result<(), AppError> {
82 let lines = stage::clean(&args.path)?;
83 if args.json {
84 return output::json(&serde_json::json!({
85 "schema": "sdd.stage-clean/1",
86 "removed": args.path,
87 }));
88 }
89 for line in lines {
90 output::line(line);
91 }
92 Ok(())
93}
94
95fn resolved(ctx: &AppContext, target: Utf8PathBuf) -> Result<Utf8PathBuf, AppError> {
97 if target.is_absolute() {
98 Ok(target)
99 } else if target == "." {
100 Ok(ctx.cwd.clone())
101 } else {
102 Err(AppError::Usage("target must be absolute or .".to_string()))
103 }
104}