1use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone)]
22pub struct DiscoveredFile {
23 pub id: FileId,
25 pub path: PathBuf,
27 pub size_bytes: u64,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
49pub struct FileId(pub u32);
50
51const _: () = assert!(std::mem::size_of::<FileId>() == 4);
52#[cfg(all(target_pointer_width = "64", unix))]
53const _: () = assert!(std::mem::size_of::<DiscoveredFile>() == 40);
54
55#[derive(
62 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
63)]
64pub struct StableFileKey(String);
65
66impl StableFileKey {
67 #[must_use]
69 pub fn from_root_relative(root: &Path, path: &Path) -> Self {
70 let relative = path.strip_prefix(root).unwrap_or(path);
71 Self(normalize_path(relative))
72 }
73
74 #[must_use]
76 pub fn from_relative(path: &Path) -> Self {
77 Self(normalize_path(path))
78 }
79
80 #[must_use]
82 pub fn as_str(&self) -> &str {
83 &self.0
84 }
85}
86
87fn normalize_path(path: &Path) -> String {
88 path.to_string_lossy().replace('\\', "/")
89}
90
91#[derive(Debug, Clone)]
93pub struct EntryPoint {
94 pub path: PathBuf,
96 pub source: EntryPointSource,
98}
99
100impl std::fmt::Display for EntryPointSource {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::PackageJsonMain => f.write_str("package.json main"),
104 Self::PackageJsonModule => f.write_str("package.json module"),
105 Self::PackageJsonExports => f.write_str("package.json exports"),
106 Self::PackageJsonBin => f.write_str("package.json bin"),
107 Self::PackageJsonScript => f.write_str("package.json script"),
108 Self::Plugin { name } => write!(f, "{name}"),
109 Self::TestFile => f.write_str("test file"),
110 Self::DefaultIndex => f.write_str("default index"),
111 Self::ManualEntry => f.write_str("manual entry"),
112 Self::InfrastructureConfig => f.write_str("infrastructure config"),
113 Self::DynamicallyLoaded => f.write_str("dynamically loaded"),
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
120pub enum EntryPointSource {
121 PackageJsonMain,
123 PackageJsonModule,
125 PackageJsonExports,
127 PackageJsonBin,
129 PackageJsonScript,
131 Plugin {
133 name: String,
135 },
136 TestFile,
138 DefaultIndex,
140 ManualEntry,
142 InfrastructureConfig,
144 DynamicallyLoaded,
146}
147
148#[cfg(test)]
149mod stable_file_key_tests {
150 use super::*;
151
152 #[test]
153 fn stable_file_key_strips_root_prefix() {
154 let key = StableFileKey::from_root_relative(
155 Path::new("/project"),
156 Path::new("/project/src/index.ts"),
157 );
158
159 assert_eq!(key.as_str(), "src/index.ts");
160 }
161
162 #[test]
163 fn stable_file_key_keeps_path_when_outside_root() {
164 let key =
165 StableFileKey::from_root_relative(Path::new("/project"), Path::new("/other/file.ts"));
166
167 assert_eq!(key.as_str(), "/other/file.ts");
168 }
169
170 #[test]
171 fn stable_file_key_normalizes_windows_separators() {
172 let key = StableFileKey::from_relative(Path::new(r"src\feature\file.ts"));
173
174 assert_eq!(key.as_str(), "src/feature/file.ts");
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn entry_point_source_display_all_variants() {
184 assert_eq!(
185 EntryPointSource::PackageJsonMain.to_string(),
186 "package.json main"
187 );
188 assert_eq!(
189 EntryPointSource::PackageJsonModule.to_string(),
190 "package.json module"
191 );
192 assert_eq!(
193 EntryPointSource::PackageJsonExports.to_string(),
194 "package.json exports"
195 );
196 assert_eq!(
197 EntryPointSource::PackageJsonBin.to_string(),
198 "package.json bin"
199 );
200 assert_eq!(
201 EntryPointSource::PackageJsonScript.to_string(),
202 "package.json script"
203 );
204 assert_eq!(
205 EntryPointSource::Plugin {
206 name: "vitest".to_string()
207 }
208 .to_string(),
209 "vitest"
210 );
211 assert_eq!(EntryPointSource::TestFile.to_string(), "test file");
212 assert_eq!(EntryPointSource::DefaultIndex.to_string(), "default index");
213 assert_eq!(EntryPointSource::ManualEntry.to_string(), "manual entry");
214 assert_eq!(
215 EntryPointSource::InfrastructureConfig.to_string(),
216 "infrastructure config"
217 );
218 assert_eq!(
219 EntryPointSource::DynamicallyLoaded.to_string(),
220 "dynamically loaded"
221 );
222 }
223}