Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ flate2 = "1.0"
tar = "0.4"
hex = "0.4"
fs2 = "0.4"
tempfile = "3.10"



3 changes: 3 additions & 0 deletions crates/corex-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ serde = { workspace = true }
serde_json = { workspace = true }


[dev-dependencies]
tempfile = { workspace = true }

[lints]
workspace = true

Expand Down
51 changes: 51 additions & 0 deletions crates/corex-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,31 @@ fn execute(parsed: ParsedArgs) -> Result<Option<String>, Diagnostic> {
Ok(None)
}
}
"migrate" | "import" => {
let project_root = std::env::current_dir().map_err(|e| {
Diagnostic::new(
ErrorFamily::Cli,
2,
format!("failed to read current working directory: {e}"),
)
})?;

let summary = corex_core::migrate_lockfile(&project_root)?;

if json {
let output = CliOutput::Success { data: summary };
Ok(Some(serde_json::to_string_pretty(&output).unwrap()))
} else {
println!(
"Successfully imported {} lockfile ({}) to `corex.lock.json` ({} packages).",
summary.format,
summary.source_path.display(),
summary.packages_migrated
);
println!("Invariant verified: Original foreign lockfile was preserved untouched.");
Ok(None)
}
}
"changed" => {
let project_root = std::env::current_dir().map_err(|e| {
Diagnostic::new(
Expand Down Expand Up @@ -1390,4 +1415,30 @@ mod tests {
assert_eq!(parsed.ignore_advisories, vec!["CX-ADV-2026-001".to_owned()]);
assert!(parsed.json);
}

#[test]
fn test_execute_migrate_command() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("package-lock.json"),
r#"{ "name": "test", "version": "1.0.0", "dependencies": { "express": { "version": "4.18.2" } } }"#,
)
.unwrap();

let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(tmp.path()).unwrap();

let parsed = ParsedArgs {
command: Some("migrate".to_string()),
json: true,
..default_test_parsed_args("migrate")
};

let result = execute(parsed).unwrap();
assert!(result.is_some());
assert!(tmp.path().join("corex.lock.json").exists());
assert!(tmp.path().join("package-lock.json").exists());

std::env::set_current_dir(original_dir).unwrap();
}
}
47 changes: 47 additions & 0 deletions crates/corex-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,50 @@ pub fn verify_provenance(
let verifier = corex_security::ProvenanceVerifier::new();
verifier.verify_provenance(root_dir, provenance)
}

/// Summary report of foreign lockfile migration to `corex.lock.json`.
#[derive(Clone, Debug, serde::Serialize)]
pub struct MigrationSummary {
/// Detected foreign lockfile format name ("npm", "pnpm", "yarn", "bun").
pub format: String,
/// Absolute or relative path to source lockfile.
pub source_path: std::path::PathBuf,
/// Absolute or relative path to output `corex.lock.json`.
pub output_path: std::path::PathBuf,
/// Number of package entries imported.
pub packages_migrated: usize,
/// Invariant: Foreign source lockfile was preserved untouched.
pub source_preserved: bool,
}

/// Detects foreign lockfiles in `project_root`, converts them to a canonical `corex.lock.json`,
/// and preserves the source foreign lockfile untouched.
///
/// # Errors
/// Returns `Diagnostic` if no foreign lockfile is found or writing `corex.lock.json` fails.
pub fn migrate_lockfile(
project_root: &std::path::Path,
) -> Result<MigrationSummary, corex_errors::Diagnostic> {
let (lockfile, format, source_path) = corex_lockfile::detect_and_import_foreign(project_root)?;

let json_content = lockfile.to_canonical_json()?;
let output_path = project_root.join("corex.lock.json");

std::fs::write(&output_path, json_content).map_err(|e| {
corex_errors::Diagnostic::new(
corex_errors::ErrorFamily::Lockfile,
14,
format!("failed writing `corex.lock.json`: {e}"),
)
})?;

let source_preserved = source_path.exists();

Ok(MigrationSummary {
format: format.as_str().to_string(),
source_path,
output_path,
packages_migrated: lockfile.packages.len(),
source_preserved,
})
}
1 change: 1 addition & 0 deletions crates/corex-lockfile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ hex = { workspace = true }
corex-config = { path = "../corex-config" }
corex-registry = { path = "../corex-registry" }
corex-resolver = { path = "../corex-resolver" }
tempfile = { workspace = true }

[lints]
workspace = true
Loading