Skip to main content

source_map_tauri/
output.rs

1use std::{
2    collections::BTreeSet,
3    fs,
4    io::{BufWriter, Write},
5    path::Path,
6};
7
8use anyhow::{Context, Result};
9use serde::Serialize;
10use serde_json::{json, Value};
11
12use crate::{
13    model::{ArtifactDoc, EdgeDoc, ScanSummary, WarningDoc},
14    scan::ScanBundle,
15};
16
17pub fn write_scan_bundle(output_dir: &Path, bundle: &ScanBundle) -> Result<()> {
18    fs::create_dir_all(output_dir)
19        .with_context(|| format!("failed to create {}", output_dir.display()))?;
20    write_ndjson(output_dir.join("artifacts.ndjson"), &bundle.artifacts)?;
21    write_ndjson(output_dir.join("edges.ndjson"), &bundle.edges)?;
22    write_ndjson(output_dir.join("warnings.ndjson"), &bundle.warnings)?;
23    fs::write(
24        output_dir.join("summary.json"),
25        serde_json::to_vec_pretty(&bundle.summary)?,
26    )?;
27    fs::write(
28        output_dir.join("project-info.json"),
29        serde_json::to_vec_pretty(&bundle.project_info)?,
30    )?;
31    fs::write(
32        output_dir.join("meili-settings.json"),
33        serde_json::to_vec_pretty(&default_meili_settings())?,
34    )?;
35    Ok(())
36}
37
38pub fn write_ndjson<T: Serialize>(path: impl AsRef<Path>, docs: &[T]) -> Result<()> {
39    let file = fs::File::create(path.as_ref())
40        .with_context(|| format!("failed to write {}", path.as_ref().display()))?;
41    let mut writer = BufWriter::new(file);
42    for doc in docs {
43        serde_json::to_writer(&mut writer, doc)?;
44        writer.write_all(b"\n")?;
45    }
46    writer.flush()?;
47    Ok(())
48}
49
50pub fn build_summary(
51    repo: &str,
52    artifacts: &[ArtifactDoc],
53    edges: &[EdgeDoc],
54    warnings: &[WarningDoc],
55) -> ScanSummary {
56    let artifact_kinds = artifacts
57        .iter()
58        .map(|item| item.kind.clone())
59        .collect::<BTreeSet<_>>()
60        .into_iter()
61        .collect();
62    let warning_types = warnings
63        .iter()
64        .map(|item| item.warning_type.clone())
65        .collect::<BTreeSet<_>>()
66        .into_iter()
67        .collect();
68
69    ScanSummary {
70        repo: repo.to_owned(),
71        artifact_count: artifacts.len(),
72        edge_count: edges.len(),
73        warning_count: warnings.len(),
74        artifact_kinds,
75        warning_types,
76        scanned_at: chrono::Utc::now().to_rfc3339(),
77    }
78}
79
80pub fn default_meili_settings() -> Value {
81    json!({
82        "searchableAttributes": [
83            "name",
84            "normalized_path",
85            "http_method",
86            "invoke_key",
87            "command_name",
88            "plugin_name",
89            "plugin_export",
90            "hook_name",
91            "hook_kind",
92            "event_name",
93            "channel_name",
94            "rust_fqn",
95            "component",
96            "display_name",
97            "signature",
98            "source_path",
99            "bundle_path",
100            "nearest_symbol",
101            "permissions",
102            "effective_capabilities",
103            "target_rust_commands",
104            "called_by_frontend",
105            "related_symbols",
106            "related_php_symbols",
107            "related_tests",
108            "primary_component",
109            "primary_wrapper",
110            "primary_transport",
111            "source_paths",
112            "risk_reasons",
113            "tags",
114            "comments",
115            "package_name"
116        ],
117        "filterableAttributes": [
118            "repo",
119            "kind",
120            "side",
121            "language",
122            "source_path",
123            "package_name",
124            "risk_level",
125            "contains_phi",
126            "has_related_tests",
127            "normalized_path",
128            "http_method",
129            "command_name",
130            "invoke_key",
131            "plugin_name",
132            "plugin_export",
133            "hook_name",
134            "hook_kind",
135            "component",
136            "event_name",
137            "channel_name",
138            "window_label",
139            "webview_label",
140            "capability_id",
141            "permission_id",
142            "merged_capabilities",
143            "remote_capability",
144            "from_id",
145            "to_id",
146            "from_kind",
147            "to_kind",
148            "edge_type",
149            "warning_type",
150            "severity"
151        ],
152        "sortableAttributes": ["confidence", "updated_at"],
153        "rankingRules": [
154            "words",
155            "typo",
156            "proximity",
157            "attribute",
158            "sort",
159            "exactness"
160        ]
161    })
162}