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}
161
162#[derive(Debug, Clone, Serialize)]
164#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
165#[cfg_attr(
166 feature = "schema",
167 schemars(title = "fallow coverage analyze --format json")
168)]
169pub struct CoverageAnalyzeOutput {
170 pub schema_version: CoverageAnalyzeSchemaVersion,
172 pub version: ToolVersion,
174 pub elapsed_ms: ElapsedMs,
176 pub runtime_coverage: RuntimeCoverageReport,
178 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
181 pub meta: Option<Meta>,
182}
183
184pub fn serialize_coverage_setup_json_output(
190 output: CoverageSetupOutput,
191 mode: RootEnvelopeMode,
192 analysis_run_id: Option<&str>,
193) -> Result<serde_json::Value, serde_json::Error> {
194 let mut value = serialize_named_json_output(output, "coverage-setup", mode)?;
195 attach_telemetry_meta(&mut value, analysis_run_id);
196 Ok(value)
197}
198
199#[must_use]
201pub fn build_coverage_analyze_output(
202 report: &RuntimeCoverageReport,
203 elapsed: Duration,
204 version: impl Into<String>,
205) -> CoverageAnalyzeOutput {
206 CoverageAnalyzeOutput {
207 schema_version: CoverageAnalyzeSchemaVersion::V1,
208 version: ToolVersion(version.into()),
209 elapsed_ms: ElapsedMs(u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)),
210 runtime_coverage: report.clone(),
211 meta: None,
212 }
213}
214
215pub fn serialize_coverage_analyze_json_output(
224 output: CoverageAnalyzeOutput,
225 mode: RootEnvelopeMode,
226 explain_meta: Option<serde_json::Value>,
227 analysis_run_id: Option<&str>,
228) -> Result<serde_json::Value, serde_json::Error> {
229 let mut value = serialize_named_json_output(output, "coverage-analyze", mode)?;
230 if let Some(meta) = explain_meta
231 && let Some(map) = value.as_object_mut()
232 {
233 map.insert("_meta".to_owned(), meta);
234 }
235 attach_telemetry_meta(&mut value, analysis_run_id);
236 Ok(value)
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use serde_json::json;
243
244 #[test]
245 fn coverage_setup_json_output_uses_named_root_contract() {
246 let output = CoverageSetupOutput {
247 schema_version: CoverageSetupSchemaVersion::V1,
248 framework_detected: CoverageSetupFramework::Unknown,
249 package_manager: None,
250 runtime_targets: Vec::new(),
251 members: Vec::new(),
252 config_written: None,
253 commands: Vec::new(),
254 files_to_edit: Vec::new(),
255 snippets: Vec::new(),
256 dockerfile_snippet: None,
257 next_steps: Vec::new(),
258 warnings: Vec::new(),
259 meta: None,
260 };
261
262 let value =
263 serialize_coverage_setup_json_output(output, RootEnvelopeMode::Tagged, Some("run-1"))
264 .expect("coverage setup should serialize");
265
266 assert_eq!(value["kind"], "coverage-setup");
267 assert_eq!(value["schema_version"], "1");
268 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-1");
269 }
270
271 #[test]
272 fn coverage_analyze_json_output_inserts_explain_meta_and_telemetry() {
273 let report = RuntimeCoverageReport::default();
274 let output = build_coverage_analyze_output(&report, Duration::from_millis(7), "test");
275
276 let value = serialize_coverage_analyze_json_output(
277 output,
278 RootEnvelopeMode::Tagged,
279 Some(json!({"docs": "coverage"})),
280 Some("run-2"),
281 )
282 .expect("coverage analyze should serialize");
283
284 assert_eq!(value["kind"], "coverage-analyze");
285 assert_eq!(value["schema_version"], "1");
286 assert_eq!(value["elapsed_ms"], 7);
287 assert_eq!(value["_meta"]["docs"], "coverage");
288 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-2");
289 }
290}