braze_sync/cli/
templatize.rs1use std::path::Path;
6
7use anyhow::Context as _;
8use clap::Args;
9
10use crate::config::ConfigFile;
11use crate::fs::{content_block_io, email_template_io};
12use crate::values::templatize::{templatize_body, FieldKind};
13
14#[derive(Args, Debug)]
15pub struct TemplatizeArgs {
16 #[arg(long)]
19 pub dry_run: bool,
20}
21
22pub async fn run(args: &TemplatizeArgs, cfg: &ConfigFile, config_dir: &Path) -> anyhow::Result<()> {
23 let content_blocks_root = config_dir.join(&cfg.resources.content_block.path);
24 let email_templates_root = config_dir.join(&cfg.resources.email_template.path);
25
26 let mut summary = RunSummary::default();
27 let mut content_block_rewrites: Vec<(std::path::PathBuf, crate::resource::ContentBlock)> =
28 Vec::new();
29 let mut email_template_rewrites: Vec<crate::resource::EmailTemplate> = Vec::new();
30
31 if cfg.resources.content_block.enabled && content_blocks_root.exists() {
32 let blocks = content_block_io::load_all_content_blocks(&content_blocks_root)
33 .context("loading local content_blocks for templatize")?;
34 for mut cb in blocks {
35 let result = templatize_body(&cb.content, FieldKind::ContentBlock);
36 if result.lid_rewrites + result.cb_id_rewrites == 0 {
37 if cb.content.contains("__BRAZESYNC__") {
38 summary.skipped.push(format!(
39 "content_block '{}' already templated — skipping",
40 cb.name
41 ));
42 }
43 continue;
44 }
45 for w in &result.warnings {
46 summary
47 .warnings
48 .push(format!("content_block '{}': {w}", cb.name));
49 }
50 summary.touched_resources += 1;
51 summary.lid_rewrites += result.lid_rewrites;
52 summary.cb_id_rewrites += result.cb_id_rewrites;
53 cb.content = result.new_body;
54 let target = content_blocks_root.join(format!("{}.liquid", cb.name));
55 content_block_rewrites.push((target, cb));
56 }
57 }
58
59 if cfg.resources.email_template.enabled && email_templates_root.exists() {
60 let templates = email_template_io::load_all_email_templates(&email_templates_root)
61 .context("loading local email_templates for templatize")?;
62 for mut et in templates {
63 let already_templated = et.subject.contains("__BRAZESYNC__")
64 || et.body_html.contains("__BRAZESYNC__")
65 || et.body_plaintext.contains("__BRAZESYNC__")
66 || et
67 .preheader
68 .as_deref()
69 .is_some_and(|p| p.contains("__BRAZESYNC__"));
70
71 let subject_r = templatize_body(&et.subject, FieldKind::EmailSubject);
72 let body_html_r = templatize_body(&et.body_html, FieldKind::EmailHtmlBody);
73 let body_plain_r = templatize_body(&et.body_plaintext, FieldKind::EmailPlainBody);
74 let preheader_r = et
75 .preheader
76 .as_ref()
77 .map(|p| templatize_body(p, FieldKind::EmailPreheader));
78
79 let total_rewrites = subject_r.lid_rewrites
80 + subject_r.cb_id_rewrites
81 + body_html_r.lid_rewrites
82 + body_html_r.cb_id_rewrites
83 + body_plain_r.lid_rewrites
84 + body_plain_r.cb_id_rewrites
85 + preheader_r
86 .as_ref()
87 .map(|r| r.lid_rewrites + r.cb_id_rewrites)
88 .unwrap_or(0);
89 if total_rewrites == 0 {
90 if already_templated {
91 summary.skipped.push(format!(
92 "email_template '{}' already templated — skipping",
93 et.name
94 ));
95 }
96 continue;
97 }
98
99 for (field, warnings) in [
100 ("subject", &subject_r.warnings),
101 ("body_html", &body_html_r.warnings),
102 ("body_plaintext", &body_plain_r.warnings),
103 ] {
104 for w in warnings {
105 summary
106 .warnings
107 .push(format!("email_template '{}' ({field}): {w}", et.name));
108 }
109 }
110 if let Some(r) = preheader_r.as_ref() {
111 for w in &r.warnings {
112 summary
113 .warnings
114 .push(format!("email_template '{}' (preheader): {w}", et.name));
115 }
116 }
117
118 summary.touched_resources += 1;
119 summary.lid_rewrites += subject_r.lid_rewrites
120 + body_html_r.lid_rewrites
121 + body_plain_r.lid_rewrites
122 + preheader_r.as_ref().map(|r| r.lid_rewrites).unwrap_or(0);
123 summary.cb_id_rewrites += subject_r.cb_id_rewrites
124 + body_html_r.cb_id_rewrites
125 + body_plain_r.cb_id_rewrites
126 + preheader_r.as_ref().map(|r| r.cb_id_rewrites).unwrap_or(0);
127
128 et.subject = subject_r.new_body;
129 et.body_html = body_html_r.new_body;
130 et.body_plaintext = body_plain_r.new_body;
131 if let Some(r) = preheader_r {
132 et.preheader = Some(r.new_body);
133 }
134 email_template_rewrites.push(et);
135 }
136 }
137
138 eprintln!("templatize summary:");
139 eprintln!(
140 " • touched {} resource(s); {} lid + {} cb_id rewrite(s)",
141 summary.touched_resources, summary.lid_rewrites, summary.cb_id_rewrites
142 );
143 for s in &summary.skipped {
144 eprintln!(" • {s}");
145 }
146 for w in &summary.warnings {
147 eprintln!(" ⚠ {w}");
148 }
149
150 if summary.touched_resources == 0 {
151 eprintln!("nothing to templatize.");
152 return Ok(());
153 }
154
155 if args.dry_run {
156 eprintln!(
157 "(dry-run) would rewrite {} resource file(s) in place",
158 content_block_rewrites.len() + email_template_rewrites.len()
159 );
160 return Ok(());
161 }
162
163 for (path, cb) in &content_block_rewrites {
164 content_block_io::save_content_block(path.parent().unwrap_or_else(|| Path::new(".")), cb)?;
165 }
166 for et in &email_template_rewrites {
167 email_template_io::save_email_template(&email_templates_root, et)?;
168 }
169 eprintln!(
170 "✓ templatize: rewrote {} resource file(s)",
171 content_block_rewrites.len() + email_template_rewrites.len()
172 );
173 Ok(())
174}
175
176#[derive(Default)]
177struct RunSummary {
178 touched_resources: usize,
179 lid_rewrites: usize,
180 cb_id_rewrites: usize,
181 skipped: Vec<String>,
182 warnings: Vec<String>,
183}