use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct DiscoveredFile {
pub id: FileId,
pub path: PathBuf,
pub size_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileId(pub u32);
const _: () = assert!(std::mem::size_of::<FileId>() == 4);
#[cfg(all(target_pointer_width = "64", unix))]
const _: () = assert!(std::mem::size_of::<DiscoveredFile>() == 40);
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct StableFileKey(String);
impl StableFileKey {
#[must_use]
pub fn from_root_relative(root: &Path, path: &Path) -> Self {
let relative = path.strip_prefix(root).unwrap_or(path);
Self(normalize_path(relative))
}
#[must_use]
pub fn from_relative(path: &Path) -> Self {
Self(normalize_path(path))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
fn normalize_path(path: &Path) -> String {
path.to_string_lossy().replace('\\', "/")
}
#[derive(Debug, Clone)]
pub struct EntryPoint {
pub path: PathBuf,
pub source: EntryPointSource,
}
impl std::fmt::Display for EntryPointSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PackageJsonMain => f.write_str("package.json main"),
Self::PackageJsonModule => f.write_str("package.json module"),
Self::PackageJsonExports => f.write_str("package.json exports"),
Self::PackageJsonBin => f.write_str("package.json bin"),
Self::PackageJsonScript => f.write_str("package.json script"),
Self::Plugin { name } => write!(f, "{name}"),
Self::TestFile => f.write_str("test file"),
Self::DefaultIndex => f.write_str("default index"),
Self::ManualEntry => f.write_str("manual entry"),
Self::InfrastructureConfig => f.write_str("infrastructure config"),
Self::DynamicallyLoaded => f.write_str("dynamically loaded"),
}
}
}
#[derive(Debug, Clone)]
pub enum EntryPointSource {
PackageJsonMain,
PackageJsonModule,
PackageJsonExports,
PackageJsonBin,
PackageJsonScript,
Plugin {
name: String,
},
TestFile,
DefaultIndex,
ManualEntry,
InfrastructureConfig,
DynamicallyLoaded,
}
#[cfg(test)]
mod stable_file_key_tests {
use super::*;
#[test]
fn stable_file_key_strips_root_prefix() {
let key = StableFileKey::from_root_relative(
Path::new("/project"),
Path::new("/project/src/index.ts"),
);
assert_eq!(key.as_str(), "src/index.ts");
}
#[test]
fn stable_file_key_keeps_path_when_outside_root() {
let key =
StableFileKey::from_root_relative(Path::new("/project"), Path::new("/other/file.ts"));
assert_eq!(key.as_str(), "/other/file.ts");
}
#[test]
fn stable_file_key_normalizes_windows_separators() {
let key = StableFileKey::from_relative(Path::new(r"src\feature\file.ts"));
assert_eq!(key.as_str(), "src/feature/file.ts");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entry_point_source_display_all_variants() {
assert_eq!(
EntryPointSource::PackageJsonMain.to_string(),
"package.json main"
);
assert_eq!(
EntryPointSource::PackageJsonModule.to_string(),
"package.json module"
);
assert_eq!(
EntryPointSource::PackageJsonExports.to_string(),
"package.json exports"
);
assert_eq!(
EntryPointSource::PackageJsonBin.to_string(),
"package.json bin"
);
assert_eq!(
EntryPointSource::PackageJsonScript.to_string(),
"package.json script"
);
assert_eq!(
EntryPointSource::Plugin {
name: "vitest".to_string()
}
.to_string(),
"vitest"
);
assert_eq!(EntryPointSource::TestFile.to_string(), "test file");
assert_eq!(EntryPointSource::DefaultIndex.to_string(), "default index");
assert_eq!(EntryPointSource::ManualEntry.to_string(), "manual entry");
assert_eq!(
EntryPointSource::InfrastructureConfig.to_string(),
"infrastructure config"
);
assert_eq!(
EntryPointSource::DynamicallyLoaded.to_string(),
"dynamically loaded"
);
}
}