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,
15 pub framework_detected: CoverageSetupFramework,
16 pub package_manager: Option<CoverageSetupPackageManager>,
17 pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
18 pub members: Vec<CoverageSetupMember>,
19 pub config_written: Option<serde_json::Value>,
20 pub commands: Vec<String>,
21 pub files_to_edit: Vec<CoverageSetupFileToEdit>,
22 pub snippets: Vec<CoverageSetupSnippet>,
23 pub dockerfile_snippet: Option<String>,
24 pub next_steps: Vec<String>,
25 pub warnings: Vec<String>,
26 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
27 pub meta: Option<serde_json::Value>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32pub enum CoverageSetupSchemaVersion {
33 #[serde(rename = "1")]
34 V1,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
38#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
39#[serde(rename_all = "snake_case")]
40pub enum CoverageSetupFramework {
41 #[serde(rename = "nextjs")]
42 NextJs,
43 #[serde(rename = "nestjs")]
44 NestJs,
45 Nuxt,
46 #[serde(rename = "sveltekit")]
47 SvelteKit,
48 Astro,
49 Remix,
50 Vite,
51 PlainNode,
52 Unknown,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
56#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
57#[serde(rename_all = "lowercase")]
58pub enum CoverageSetupPackageManager {
59 Npm,
60 Pnpm,
61 Yarn,
62 Bun,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
66#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
67#[serde(rename_all = "lowercase")]
68pub enum CoverageSetupRuntimeTarget {
69 Node,
70 Browser,
71}
72
73#[derive(Debug, Clone, Serialize)]
74#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
75pub struct CoverageSetupMember {
76 pub name: String,
77 pub path: String,
78 pub framework_detected: CoverageSetupFramework,
79 pub package_manager: Option<CoverageSetupPackageManager>,
80 pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
81 pub files_to_edit: Vec<CoverageSetupFileToEdit>,
82 pub snippets: Vec<CoverageSetupSnippet>,
83 pub dockerfile_snippet: Option<String>,
84 pub warnings: Vec<String>,
85}
86
87#[derive(Debug, Clone, Serialize)]
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
89pub struct CoverageSetupFileToEdit {
90 pub path: String,
91 pub reason: String,
92}
93
94#[derive(Debug, Clone, Serialize)]
95#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
96pub struct CoverageSetupSnippet {
97 pub label: String,
98 pub path: String,
99 pub content: String,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
103#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
104pub enum CoverageAnalyzeSchemaVersion {
105 #[serde(rename = "1")]
106 V1,
107}
108
109#[derive(Debug, Clone, Serialize)]
110#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
111#[cfg_attr(
112 feature = "schema",
113 schemars(title = "fallow coverage analyze --format json")
114)]
115pub struct CoverageAnalyzeOutput {
116 pub schema_version: CoverageAnalyzeSchemaVersion,
117 pub version: ToolVersion,
118 pub elapsed_ms: ElapsedMs,
119 pub runtime_coverage: RuntimeCoverageReport,
120 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
121 pub meta: Option<Meta>,
122}
123
124pub fn serialize_coverage_setup_json_output(
130 output: CoverageSetupOutput,
131 mode: RootEnvelopeMode,
132 analysis_run_id: Option<&str>,
133) -> Result<serde_json::Value, serde_json::Error> {
134 let mut value = serialize_named_json_output(output, "coverage-setup", mode)?;
135 attach_telemetry_meta(&mut value, analysis_run_id);
136 Ok(value)
137}
138
139#[must_use]
141pub fn build_coverage_analyze_output(
142 report: &RuntimeCoverageReport,
143 elapsed: Duration,
144 version: impl Into<String>,
145) -> CoverageAnalyzeOutput {
146 CoverageAnalyzeOutput {
147 schema_version: CoverageAnalyzeSchemaVersion::V1,
148 version: ToolVersion(version.into()),
149 elapsed_ms: ElapsedMs(u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)),
150 runtime_coverage: report.clone(),
151 meta: None,
152 }
153}
154
155pub fn serialize_coverage_analyze_json_output(
164 output: CoverageAnalyzeOutput,
165 mode: RootEnvelopeMode,
166 explain_meta: Option<serde_json::Value>,
167 analysis_run_id: Option<&str>,
168) -> Result<serde_json::Value, serde_json::Error> {
169 let mut value = serialize_named_json_output(output, "coverage-analyze", mode)?;
170 if let Some(meta) = explain_meta
171 && let Some(map) = value.as_object_mut()
172 {
173 map.insert("_meta".to_owned(), meta);
174 }
175 attach_telemetry_meta(&mut value, analysis_run_id);
176 Ok(value)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use serde_json::json;
183
184 #[test]
185 fn coverage_setup_json_output_uses_named_root_contract() {
186 let output = CoverageSetupOutput {
187 schema_version: CoverageSetupSchemaVersion::V1,
188 framework_detected: CoverageSetupFramework::Unknown,
189 package_manager: None,
190 runtime_targets: Vec::new(),
191 members: Vec::new(),
192 config_written: None,
193 commands: Vec::new(),
194 files_to_edit: Vec::new(),
195 snippets: Vec::new(),
196 dockerfile_snippet: None,
197 next_steps: Vec::new(),
198 warnings: Vec::new(),
199 meta: None,
200 };
201
202 let value =
203 serialize_coverage_setup_json_output(output, RootEnvelopeMode::Tagged, Some("run-1"))
204 .expect("coverage setup should serialize");
205
206 assert_eq!(value["kind"], "coverage-setup");
207 assert_eq!(value["schema_version"], "1");
208 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-1");
209 }
210
211 #[test]
212 fn coverage_analyze_json_output_inserts_explain_meta_and_telemetry() {
213 let report = RuntimeCoverageReport::default();
214 let output = build_coverage_analyze_output(&report, Duration::from_millis(7), "test");
215
216 let value = serialize_coverage_analyze_json_output(
217 output,
218 RootEnvelopeMode::Tagged,
219 Some(json!({"docs": "coverage"})),
220 Some("run-2"),
221 )
222 .expect("coverage analyze should serialize");
223
224 assert_eq!(value["kind"], "coverage-analyze");
225 assert_eq!(value["schema_version"], "1");
226 assert_eq!(value["elapsed_ms"], 7);
227 assert_eq!(value["_meta"]["docs"], "coverage");
228 assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-2");
229 }
230}