1#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
8
9use std::path::Path;
10
11use presolve_compiler::persistent_cache::CacheReportSelector;
12use presolve_compiler::platform::{
13 derive_workspace_id_v1, ContractVersion, RequestedCompilationMode, WorkspaceConfiguration,
14 WorkspaceInput, WorkspaceSnapshot, WorkspaceSource,
15};
16use presolve_compiler::service::{
17 CompileRequest, CompilerServiceHost, CompleteSource, IncrementalReportSelector, ServiceError,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CliSourceInputV1 {
24 pub logical_path: String,
25 pub content: String,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct CliCompilationCandidateV1 {
32 pub configuration: WorkspaceConfiguration,
33 pub sources: Vec<CliSourceInputV1>,
34 pub verify_clean_equivalence: bool,
35 pub report: IncrementalReportSelector,
36 pub cache_report: CacheReportSelector,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct CliCompilationResultV1 {
41 pub workspace_id: String,
42 pub commit_sequence: u64,
43 pub snapshot_id: String,
44 pub graph_snapshot_id: String,
45 pub mode: String,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct CliCompilationErrorV1 {
50 pub code: &'static str,
51 pub message: String,
52}
53
54fn compiler_contract() -> ContractVersion {
55 ContractVersion::new(format!("presolve-compiler:{}", env!("CARGO_PKG_VERSION")))
56}
57
58fn service_error(error: ServiceError) -> CliCompilationErrorV1 {
59 CliCompilationErrorV1 {
60 code: error.code,
61 message: error.message,
62 }
63}
64
65fn candidate_snapshot(
66 candidate: &CliCompilationCandidateV1,
67 contract: &ContractVersion,
68) -> Result<WorkspaceSnapshot, CliCompilationErrorV1> {
69 WorkspaceSnapshot::from_input(&WorkspaceInput {
70 configuration: candidate.configuration.clone(),
71 sources: candidate
72 .sources
73 .iter()
74 .map(|source| WorkspaceSource {
75 path: source.logical_path.clone(),
76 source: source.content.clone(),
77 language: None,
78 })
79 .collect(),
80 compiler_contract: contract.clone(),
81 })
82 .map_err(|error| CliCompilationErrorV1 {
83 code: "L9C201_INVALID_COMPLETE_CANDIDATE",
84 message: error.message,
85 })
86}
87
88pub fn compile_complete_candidate_v1(
93 service_root: &Path,
94 candidate: CliCompilationCandidateV1,
95) -> Result<CliCompilationResultV1, CliCompilationErrorV1> {
96 let contract = compiler_contract();
97 let workspace_id = derive_workspace_id_v1(&candidate.configuration).map_err(|error| {
98 CliCompilationErrorV1 {
99 code: "L9C202_INVALID_CONFIGURATION",
100 message: error.message,
101 }
102 })?;
103 let snapshot = candidate_snapshot(&candidate, &contract)?;
104 let mut service = CompilerServiceHost::start(service_root, contract).map_err(service_error)?;
105 let session_id = service
106 .open_session(candidate.configuration.clone(), &workspace_id)
107 .map_err(service_error)?;
108 let response = service
109 .compile(
110 &session_id,
111 0,
112 CompileRequest {
113 configuration: candidate.configuration,
114 candidate_snapshot: snapshot,
115 sources: candidate
116 .sources
117 .into_iter()
118 .map(|source| CompleteSource {
119 path: source.logical_path,
120 source: source.content,
121 language: None,
122 })
123 .collect(),
124 mode: RequestedCompilationMode::Automatic,
125 incremental_report: candidate.report,
126 verify_exact_equivalence: candidate.verify_clean_equivalence,
127 cache_report: candidate.cache_report,
128 },
129 )
130 .map_err(service_error)?;
131 service.close_session(&session_id).map_err(service_error)?;
132 Ok(CliCompilationResultV1 {
133 workspace_id: workspace_id.to_string(),
134 commit_sequence: response.commit_sequence,
135 snapshot_id: response.snapshot.snapshot_id.to_string(),
136 graph_snapshot_id: response.graph.snapshot_id.to_string(),
137 mode: response.mode,
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use std::sync::atomic::{AtomicU64, Ordering};
144
145 use super::*;
146
147 static NEXT_ROOT: AtomicU64 = AtomicU64::new(1);
148
149 fn candidate() -> CliCompilationCandidateV1 {
150 CliCompilationCandidateV1 {
151 configuration: WorkspaceConfiguration::default(),
152 sources: vec![CliSourceInputV1 {
153 logical_path: "src/main.ts".into(),
154 content: "export const L9_C_SOURCE_SENTINEL = 1;\n".into(),
155 }],
156 verify_clean_equivalence: true,
157 report: IncrementalReportSelector::Summary,
158 cache_report: CacheReportSelector::Summary,
159 }
160 }
161
162 #[test]
163 fn l9c_delegates_complete_caller_owned_source_input_to_l4() {
164 let root = std::env::temp_dir().join(format!(
165 "presolve-l9c-{}",
166 NEXT_ROOT.fetch_add(1, Ordering::Relaxed)
167 ));
168 let result = compile_complete_candidate_v1(&root, candidate()).unwrap();
169 assert_eq!(result.commit_sequence, 1);
170 assert!(result.snapshot_id.starts_with("snapshot:"));
171 assert_eq!(result.snapshot_id, result.graph_snapshot_id);
172 let session = std::fs::read_dir(root.join("service").join("sessions"))
173 .unwrap()
174 .next()
175 .unwrap()
176 .unwrap()
177 .path()
178 .join("session.json");
179 let durable = std::fs::read_to_string(session).unwrap();
180 assert!(!durable.contains("L9_C_SOURCE_SENTINEL"));
181 std::fs::remove_dir_all(root).unwrap();
182 }
183}