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            "invoke_key",
85            "command_name",
86            "plugin_name",
87            "plugin_export",
88            "hook_name",
89            "hook_kind",
90            "event_name",
91            "channel_name",
92            "rust_fqn",
93            "component",
94            "display_name",
95            "signature",
96            "source_path",
97            "bundle_path",
98            "nearest_symbol",
99            "permissions",
100            "effective_capabilities",
101            "target_rust_commands",
102            "called_by_frontend",
103            "related_symbols",
104            "related_php_symbols",
105            "related_tests",
106            "risk_reasons",
107            "tags",
108            "comments",
109            "package_name"
110        ],
111        "filterableAttributes": [
112            "repo",
113            "kind",
114            "side",
115            "language",
116            "source_path",
117            "package_name",
118            "risk_level",
119            "contains_phi",
120            "has_related_tests",
121            "command_name",
122            "invoke_key",
123            "plugin_name",
124            "plugin_export",
125            "hook_name",
126            "hook_kind",
127            "component",
128            "event_name",
129            "channel_name",
130            "window_label",
131            "webview_label",
132            "capability_id",
133            "permission_id",
134            "merged_capabilities",
135            "remote_capability",
136            "from_id",
137            "to_id",
138            "from_kind",
139            "to_kind",
140            "edge_type",
141            "warning_type",
142            "severity"
143        ],
144        "sortableAttributes": ["confidence", "updated_at"],
145        "rankingRules": [
146            "words",
147            "typo",
148            "proximity",
149            "attribute",
150            "sort",
151            "exactness"
152        ]
153    })
154}