use clap::Parser;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use crate::args::CliArgs;
use crate::build;
use crate::project_refs::ResolvedProject;
fn create_test_project(dir: &Path, name: &str, config: &str) -> PathBuf {
let project_dir = dir.join(name);
std::fs::create_dir_all(&project_dir).unwrap();
let config_path = project_dir.join("tsconfig.json");
std::fs::write(&config_path, config).unwrap();
project_dir
}
fn create_source_file(project_dir: &Path, name: &str, content: &str) -> PathBuf {
let src_dir = project_dir.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
let file_path = src_dir.join(name);
std::fs::write(&file_path, content).unwrap();
file_path
}
#[test]
fn test_is_project_up_to_date_no_buildinfo() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(
temp_dir.path(),
"test",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: true,
out_dir: Some(project_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz"]).unwrap();
assert!(!build::is_project_up_to_date(&project, &args));
}
#[test]
#[ignore = "is_project_up_to_date implementation incomplete - needs .tsbuildinfo parsing and validation"]
fn test_is_project_up_to_date_with_buildinfo() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(
temp_dir.path(),
"test",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let buildinfo_path = project_dir.join("tsconfig.tsbuildinfo");
let compiler_version = env!("CARGO_PKG_VERSION");
let buildinfo_content = format!(
r#"{{
"version": "0.1.0",
"compilerVersion": "{compiler_version}",
"rootFiles": [],
"fileInfos": {{}},
"dependencies": {{}},
"semanticDiagnosticsPerFile": {{}},
"emitSignatures": {{}},
"latestChangedDtsFile": null,
"options": {{}},
"buildTime": 1234567890
}}"#
);
std::fs::write(&buildinfo_path, buildinfo_content).unwrap();
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: true,
out_dir: Some(project_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz"]).unwrap();
assert!(build::is_project_up_to_date(&project, &args));
}
#[test]
fn test_is_project_up_to_date_force_rebuild() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(
temp_dir.path(),
"test",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let buildinfo_path = project_dir.join("tsconfig.tsbuildinfo");
std::fs::write(&buildinfo_path, "{}").unwrap();
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: true,
out_dir: Some(project_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz", "--force"]).unwrap();
assert!(!build::is_project_up_to_date(&project, &args));
}
#[test]
fn test_get_build_info_path() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(temp_dir.path(), "myproject", "{}");
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: false,
out_dir: None,
declaration_dir: None,
};
assert!(project.config_path.exists());
assert_eq!(project.config_path.file_name().unwrap(), "tsconfig.json");
}
#[test]
fn test_is_project_up_to_date_with_source_changes() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(
temp_dir.path(),
"test",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let _source_file = create_source_file(&project_dir, "index.ts", "export const x = 1;");
let buildinfo_path = project_dir.join("tsconfig.tsbuildinfo");
let compiler_version = env!("CARGO_PKG_VERSION");
let buildinfo_content = format!(
r#"{{
"version": "0.1.0",
"compilerVersion": "{compiler_version}",
"rootFiles": ["src/index.ts"],
"fileInfos": {{
"src/index.ts": {{
"version": "oldhash",
"signature": null
}}
}},
"dependencies": {{}},
"semanticDiagnosticsPerFile": {{}},
"emitSignatures": {{}},
"latestChangedDtsFile": null,
"options": {{}},
"buildTime": 1234567890
}}"#
);
std::fs::write(&buildinfo_path, buildinfo_content).unwrap();
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: true,
out_dir: Some(project_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz"]).unwrap();
assert!(!build::is_project_up_to_date(&project, &args));
}
#[test]
fn test_is_project_up_to_date_with_new_source_files() {
let temp_dir = TempDir::new().unwrap();
let project_dir = create_test_project(
temp_dir.path(),
"test",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
create_source_file(&project_dir, "index.ts", "export const x = 1;");
let buildinfo_path = project_dir.join("tsconfig.tsbuildinfo");
let compiler_version = env!("CARGO_PKG_VERSION");
let buildinfo_content = format!(
r#"{{
"version": "0.1.0",
"compilerVersion": "{compiler_version}",
"rootFiles": [],
"fileInfos": {{}},
"dependencies": {{}},
"semanticDiagnosticsPerFile": {{}},
"emitSignatures": {{}},
"latestChangedDtsFile": null,
"options": {{}},
"buildTime": 1234567890
}}"#
);
std::fs::write(&buildinfo_path, buildinfo_content).unwrap();
let project = ResolvedProject {
config_path: project_dir.join("tsconfig.json"),
root_dir: project_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![],
is_composite: true,
out_dir: Some(project_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz"]).unwrap();
assert!(!build::is_project_up_to_date(&project, &args));
}
#[test]
fn test_is_project_up_to_date_cross_project_invalidation() {
let temp_dir = TempDir::new().unwrap();
let main_dir = create_test_project(
temp_dir.path(),
"main",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let ref_dir = create_test_project(
temp_dir.path(),
"ref",
r#"
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
"#,
);
let ref_dist_dir = ref_dir.join("dist");
std::fs::create_dir_all(&ref_dist_dir).unwrap();
let ref_dts_path = ref_dist_dir.join("index.d.ts");
std::fs::write(&ref_dts_path, "export const x = 1;").unwrap();
let ref_buildinfo_path = ref_dir.join("tsconfig.tsbuildinfo");
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let compiler_version = env!("CARGO_PKG_VERSION");
let ref_buildinfo_content = format!(
r#"{{
"version": "0.1.0",
"compilerVersion": "{compiler_version}",
"rootFiles": [],
"fileInfos": {{}},
"dependencies": {{}},
"semanticDiagnosticsPerFile": {{}},
"emitSignatures": {{}},
"latestChangedDtsFile": "dist/index.d.ts",
"options": {{}},
"buildTime": {current_time}
}}"#
);
std::fs::write(&ref_buildinfo_path, ref_buildinfo_content).unwrap();
let main_buildinfo_path = main_dir.join("tsconfig.tsbuildinfo");
let old_time = current_time - 3600; let main_buildinfo_content = format!(
r#"{{
"version": "0.1.0",
"compilerVersion": "{compiler_version}",
"rootFiles": [],
"fileInfos": {{}},
"dependencies": {{}},
"semanticDiagnosticsPerFile": {{}},
"emitSignatures": {{}},
"latestChangedDtsFile": null,
"options": {{}},
"buildTime": {old_time}
}}"#
);
std::fs::write(&main_buildinfo_path, main_buildinfo_content).unwrap();
use crate::project_refs::{ProjectReference, ResolvedProjectReference};
let resolved_ref = ResolvedProjectReference {
config_path: ref_dir.join("tsconfig.json"),
original: ProjectReference {
path: "../ref".to_string(),
prepend: false,
circular: false,
},
is_valid: true,
error: None,
};
let project = ResolvedProject {
config_path: main_dir.join("tsconfig.json"),
root_dir: main_dir.clone(),
config: serde_json::from_str("{}").unwrap(),
resolved_references: vec![resolved_ref],
is_composite: true,
out_dir: Some(main_dir.join("dist")),
declaration_dir: None,
};
let args = CliArgs::try_parse_from(["tsz"]).unwrap();
assert!(!build::is_project_up_to_date(&project, &args));
}