1use crate::RuntimeCoverageReport;
4use crate::root_envelopes::{RootEnvelopeMode, attach_telemetry_meta, serialize_named_json_output};
5use fallow_types::envelope::{ElapsedMs, Meta, ToolVersion};
6use serde::Serialize;
7use std::time::Duration;
8
9#[derive(Debug, Clone, Serialize)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[cfg_attr(feature = "schema", schemars(title = "fallow coverage setup --json"))]
13pub struct CoverageSetupOutput {
14 pub schema_version: CoverageSetupSchemaVersion,
16 pub framework_detected: CoverageSetupFramework,
18 pub package_manager: Option<CoverageSetupPackageManager>,
20 pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
22 pub members: Vec<CoverageSetupMember>,
24 pub config_written: Option<serde_json::Value>,
26 pub commands: Vec<String>,
28 pub files_to_edit: Vec<CoverageSetupFileToEdit>,
30 pub snippets: Vec<CoverageSetupSnippet>,
32 pub dockerfile_snippet: Option<String>,
34 pub next_steps: Vec<String>,
36 pub warnings: Vec<String>,
38 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
40 pub meta: Option<serde_json::Value>,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
45#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
46pub enum CoverageSetupSchemaVersion {
47 #[serde(rename = "1")]
49 V1,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
55#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
56#[serde(rename_all = "snake_case")]
57pub enum CoverageSetupFramework {
58 #[serde(rename = "nextjs")]
60 NextJs,
61 #[serde(rename = "nestjs")]
63 NestJs,
64 Nuxt,
66 #[serde(rename = "sveltekit")]
68 SvelteKit,
69 Astro,
71 Remix,
73 Vite,
75 PlainNode,
77 Unknown,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
83#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
84#[serde(rename_all = "lowercase")]
85pub enum CoverageSetupPackageManager {
86 Npm,
88 Pnpm,
90 Yarn,
92 Bun,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
98#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
99#[serde(rename_all = "lowercase")]
100pub enum CoverageSetupRuntimeTarget {
101 Node,
103 Browser,
105}
106
107#[derive(Debug, Clone, Serialize)]
109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
110pub struct CoverageSetupMember {
111 pub name: String,
113 pub path: String,
115 pub framework_detected: CoverageSetupFramework,
117 pub package_manager: Option<CoverageSetupPackageManager>,
119 pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
121 pub files_to_edit: Vec<CoverageSetupFileToEdit>,
123 pub snippets: Vec<CoverageSetupSnippet>,
125 pub dockerfile_snippet: Option<String>,
127 pub warnings: Vec<String>,
129}
130
131#[derive(Debug, Clone, Serialize)]
133#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
134pub struct CoverageSetupFileToEdit {
135 pub path: String,
137 pub reason: String,
139}
140
141#[derive(Debug, Clone, Serialize)]
143#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
144pub struct CoverageSetupSnippet {
145 pub label: String,
147 pub path: String,
149 pub content: String,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
156pub enum CoverageAnalyzeSchemaVersion {
157 #[serde(rename = "1")]
159 V1,
160 #[serde(rename = "2")]
162 V2,
163}
164
165#[derive(Debug, Clone, Serialize)]
167#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
168#[cfg_attr(
169 feature = "schema",
170 schemars(title = "fallow coverage analyze --format json")
171)]
172pub struct CoverageAnalyzeOutput {
173 pub schema_version: CoverageAnalyzeSchemaVersion,
175 pub version: ToolVersion,
177 pub elapsed_ms: ElapsedMs,
179 pub runtime_coverage: RuntimeCoverageReport,
181 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
184 pub meta: Option<Meta>,
185}
186
187pub fn serialize_coverage_setup_json_output(
193 output: CoverageSetupOutput,
194 mode: RootEnvelopeMode,
195 analysis_run_id: Option<&str>,
196) -> Result<serde_json::Value, serde_json::Error> {
197 let mut value = serialize_named_json_output(output, "coverage-setup", mode)?;
198 attach_telemetry_meta(&mut value, analysis_run_id);
199 Ok(value)
200}
201
202#[must_use]
204pub fn build_coverage_analyze_output(
205 report: &RuntimeCoverageReport,
206 elapsed: Duration,
207 version: impl Into<String>,
208) -> CoverageAnalyzeOutput {
209 CoverageAnalyzeOutput {
210 schema_version: CoverageAnalyzeSchemaVersion::V2,
211 version: ToolVersion(version.into()),
212 elapsed_ms: ElapsedMs(u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)),
213 runtime_coverage: report.clone(),
214 meta: None,
215 }
216}
217
218pub fn serialize_coverage_analyze_json_output(
227 output: CoverageAnalyzeOutput,
228 mode: RootEnvelopeMode,
229 explain_meta: Option<serde_json::Value>,
230 analysis_run_id: Option<&str>,
231) -> Result<serde_json::Value, serde_json::Error> {
232 let mut value = serialize_named_json_output(output, "coverage-analyze", mode)?;
233 if let Some(meta) = explain_meta
234 && let Some(map) = value.as_object_mut()
235 {
236 map.insert("_meta".to_owned(), meta);
237 }
238 attach_telemetry_meta(&mut value, analysis_run_id);
239 Ok(value)
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use serde_json::json;
246
247 #[test]
248 fn coverage_setup_json_output_uses_named_root_contract() {
249 let output = CoverageSetupOutput {
250 schema_version: CoverageSetupSchemaVersion::V1,
251 framework_detected: CoverageSetupFramework::Unknown,
252 package_manager: None,
253 runtime_targets: Vec::new(),
254 members: Vec::new(),
255 config_written: None,
256 commands: Vec::new(),
257 files_to_edit: Vec::new(),
258 snippets: Vec::new(),
259 dockerfile_snippet: None,
260 next_steps: Vec::new(),
261 warnings: Vec::new(),
262 meta: None,
263 };
264
265 let value =
266 serialize_coverage_setup_json_output(output, RootEnvelopeMode::Tagged, Some("run-1"))
267 .expect("coverage setup should serialize");
268
269 assert_eq!(value["kind"], "coverage-setup");
270 assert_eq!(value["schema_version"], "1");
271 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-1");
272 }
273
274 #[test]
275 fn coverage_analyze_json_output_inserts_explain_meta_and_telemetry() {
276 let report = RuntimeCoverageReport::default();
277 let output = build_coverage_analyze_output(&report, Duration::from_millis(7), "test");
278
279 let value = serialize_coverage_analyze_json_output(
280 output,
281 RootEnvelopeMode::Tagged,
282 Some(json!({"docs": "coverage"})),
283 Some("run-2"),
284 )
285 .expect("coverage analyze should serialize");
286
287 assert_eq!(value["kind"], "coverage-analyze");
288 assert_eq!(value["schema_version"], "2");
289 assert_eq!(value["elapsed_ms"], 7);
290 assert_eq!(value["_meta"]["docs"], "coverage");
291 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-2");
292 }
293}