systemprompt_cli/commands/core/content/
publish.rs1use super::types::{PublishPipelineOutput, StepResult};
2use crate::cli_settings::CliConfig;
3use crate::shared::CommandResult;
4use anyhow::Result;
5use clap::{Args, ValueEnum};
6use std::sync::Arc;
7use std::time::Instant;
8use systemprompt_content::ContentIngestionJob;
9use systemprompt_generator::{
10 generate_sitemap, prerender_content, prerender_homepage, CopyExtensionAssetsJob,
11};
12use systemprompt_runtime::AppContext;
13
14#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
15pub enum PublishStep {
16 Ingest,
18 Assets,
20 Prerender,
22 Homepage,
24 Sitemap,
26 All,
28}
29
30#[derive(Debug, Args)]
31pub struct PublishArgs {
32 #[arg(long, short = 's', value_enum, help = "Run specific step(s) only")]
33 pub step: Option<Vec<PublishStep>>,
34
35 #[arg(long, help = "Skip content ingestion step")]
36 pub skip_ingest: bool,
37
38 #[arg(long, help = "Show verbose output")]
39 pub verbose: bool,
40}
41
42impl PublishArgs {
43 fn should_run(&self, step: PublishStep) -> bool {
44 self.step.as_ref().map_or_else(
45 || {
46 if step == PublishStep::Ingest {
47 !self.skip_ingest
48 } else {
49 true
50 }
51 },
52 |steps| {
53 if steps.contains(&PublishStep::All) {
54 if step == PublishStep::Ingest {
55 !self.skip_ingest
56 } else {
57 true
58 }
59 } else {
60 steps.contains(&step)
61 }
62 },
63 )
64 }
65}
66
67pub async fn execute(
68 args: PublishArgs,
69 _config: &CliConfig,
70) -> Result<CommandResult<PublishPipelineOutput>> {
71 let start_time = Instant::now();
72 let verbose = args.verbose;
73
74 let ctx = AppContext::new().await?;
75 let db_pool = ctx.db_pool();
76
77 let mut steps: Vec<StepResult> = Vec::new();
78
79 if args.should_run(PublishStep::Ingest) {
80 let step_start = Instant::now();
81 if verbose {
82 tracing::info!("Starting content ingestion...");
83 }
84
85 let result = ContentIngestionJob::execute_ingestion(db_pool).await;
86 let duration_ms = step_start.elapsed().as_millis() as u64;
87
88 steps.push(StepResult {
89 step: "ingest".to_string(),
90 success: result.is_ok(),
91 duration_ms,
92 message: result.err().map(|e| e.to_string()),
93 });
94 }
95
96 if args.should_run(PublishStep::Assets) {
97 let step_start = Instant::now();
98 if verbose {
99 tracing::info!("Starting extension asset copy...");
100 }
101
102 let result = CopyExtensionAssetsJob::execute_copy().await;
103 let duration_ms = step_start.elapsed().as_millis() as u64;
104
105 steps.push(StepResult {
106 step: "assets".to_string(),
107 success: result.is_ok(),
108 duration_ms,
109 message: result.err().map(|e| e.to_string()),
110 });
111 }
112
113 if args.should_run(PublishStep::Prerender) {
114 let step_start = Instant::now();
115 if verbose {
116 tracing::info!("Starting content prerendering...");
117 }
118
119 let result = prerender_content(Arc::clone(db_pool)).await;
120 let duration_ms = step_start.elapsed().as_millis() as u64;
121
122 steps.push(StepResult {
123 step: "prerender".to_string(),
124 success: result.is_ok(),
125 duration_ms,
126 message: result.err().map(|e| e.to_string()),
127 });
128 }
129
130 if args.should_run(PublishStep::Homepage) {
131 let step_start = Instant::now();
132 if verbose {
133 tracing::info!("Starting homepage prerendering...");
134 }
135
136 let result = prerender_homepage(Arc::clone(db_pool)).await;
137 let duration_ms = step_start.elapsed().as_millis() as u64;
138
139 steps.push(StepResult {
140 step: "homepage".to_string(),
141 success: result.is_ok(),
142 duration_ms,
143 message: result.err().map(|e| e.to_string()),
144 });
145 }
146
147 if args.should_run(PublishStep::Sitemap) {
148 let step_start = Instant::now();
149 if verbose {
150 tracing::info!("Starting sitemap generation...");
151 }
152
153 let result = generate_sitemap(Arc::clone(db_pool)).await;
154 let duration_ms = step_start.elapsed().as_millis() as u64;
155
156 steps.push(StepResult {
157 step: "sitemap".to_string(),
158 success: result.is_ok(),
159 duration_ms,
160 message: result.err().map(|e| e.to_string()),
161 });
162 }
163
164 let total_duration_ms = start_time.elapsed().as_millis() as u64;
165 let succeeded = steps.iter().filter(|s| s.success).count();
166 let failed = steps.iter().filter(|s| !s.success).count();
167 let total_steps = steps.len();
168
169 let output = PublishPipelineOutput {
170 steps,
171 total_steps,
172 succeeded,
173 failed,
174 duration_ms: total_duration_ms,
175 };
176
177 let title = if failed == 0 {
178 "Content Published Successfully"
179 } else {
180 "Content Publish Completed with Errors"
181 };
182
183 Ok(CommandResult::card(output).with_title(title))
184}