mod bun;
mod npm;
mod pnpm;
pub use bun::parse_bun_lockfile;
pub use npm::parse_npm_lockfile;
pub use pnpm::parse_pnpm_lockfile;
use crate::workspace::traits::{Lockfile, LockfileDiffParser, LockfileEntry};
use super::NodePackageManager;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug, Clone)]
pub struct NodeLockEntry {
pub name: String,
pub version: String,
pub dependencies: Vec<String>,
}
impl LockfileEntry for NodeLockEntry {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
&self.version
}
fn dependencies(&self) -> &[String] {
&self.dependencies
}
}
#[derive(Debug, Clone)]
pub struct NodeLockfile {
pub entries: Vec<NodeLockEntry>,
}
impl NodeLockfile {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn parse(content: &str, manager: NodePackageManager) -> Result<Self, BoxError> {
match manager {
NodePackageManager::Npm => parse_npm_lockfile(content),
NodePackageManager::Pnpm => parse_pnpm_lockfile(content),
NodePackageManager::Bun => parse_bun_lockfile(content),
}
}
#[must_use]
pub fn find_by_name(&self, name: &str) -> Option<&NodeLockEntry> {
self.entries.iter().find(|e| e.name == name)
}
}
impl Default for NodeLockfile {
fn default() -> Self {
Self::new()
}
}
impl Lockfile for NodeLockfile {
fn entries(&self) -> Vec<Box<dyn LockfileEntry>> {
self.entries
.iter()
.cloned()
.map(|e| Box::new(e) as Box<dyn LockfileEntry>)
.collect()
}
fn find(&self, name: &str) -> Option<Box<dyn LockfileEntry>> {
self.find_by_name(name)
.cloned()
.map(|e| Box::new(e) as Box<dyn LockfileEntry>)
}
}
#[derive(Debug, Clone)]
pub struct NodeLockDiffParser {
pub manager: NodePackageManager,
}
impl NodeLockDiffParser {
#[must_use]
pub const fn new(manager: NodePackageManager) -> Self {
Self { manager }
}
}
impl Default for NodeLockDiffParser {
fn default() -> Self {
Self::new(NodePackageManager::Npm)
}
}
impl LockfileDiffParser for NodeLockDiffParser {
fn parse_changes(&self, changes: &[(char, String)]) -> Vec<String> {
match self.manager {
NodePackageManager::Npm => npm::parse_npm_lock_changes(changes),
NodePackageManager::Pnpm => pnpm::parse_pnpm_lock_changes(changes),
NodePackageManager::Bun => bun::parse_bun_lock_changes(changes),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::workspace::traits::LockfileDiffParser;
#[test]
fn test_npm_diff_parser_version_upgrade() {
let parser = NodeLockDiffParser::new(NodePackageManager::Npm);
let changes = vec![
(' ', " \"packages\": {".to_string()),
(' ', " \"node_modules/lodash\": {".to_string()),
('-', " \"version\": \"4.17.20\"".to_string()),
('+', " \"version\": \"4.17.21\"".to_string()),
(' ', " },".to_string()),
(' ', " }".to_string()),
];
let result = parser.parse_changes(&changes);
assert!(result.contains(&"lodash".to_string()));
}
#[test]
fn test_npm_diff_parser_new_package() {
let parser = NodeLockDiffParser::new(NodePackageManager::Npm);
let changes = vec![
(' ', " \"packages\": {".to_string()),
('+', " \"node_modules/new-package\": {".to_string()),
('+', " \"version\": \"1.0.0\"".to_string()),
('+', " },".to_string()),
(' ', " }".to_string()),
];
let result = parser.parse_changes(&changes);
assert!(result.contains(&"new-package".to_string()));
}
#[test]
fn test_pnpm_diff_parser_version_change() {
let parser = NodeLockDiffParser::new(NodePackageManager::Pnpm);
let changes = vec![
(' ', "packages:".to_string()),
('-', " /lodash@4.17.20:".to_string()),
('+', " /lodash@4.17.21:".to_string()),
(' ', " resolution: {integrity: sha512-abc}".to_string()),
];
let result = parser.parse_changes(&changes);
assert!(result.contains(&"lodash".to_string()));
}
#[test]
fn test_pnpm_diff_parser_scoped_package() {
let parser = NodeLockDiffParser::new(NodePackageManager::Pnpm);
let changes = vec![
(' ', "packages:".to_string()),
('+', " /@babel/core@7.23.0:".to_string()),
('+', " resolution: {integrity: sha512-abc}".to_string()),
];
let result = parser.parse_changes(&changes);
assert!(result.contains(&"@babel/core".to_string()));
}
#[test]
fn test_bun_diff_parser_version_upgrade() {
let parser = NodeLockDiffParser::new(NodePackageManager::Bun);
let changes = vec![
(' ', " \"packages\": {".to_string()),
('-', " \"lodash@4.17.20\": {".to_string()),
('-', " \"version\": \"4.17.20\"".to_string()),
('-', " },".to_string()),
('+', " \"lodash@4.17.21\": {".to_string()),
('+', " \"version\": \"4.17.21\"".to_string()),
('+', " },".to_string()),
];
let result = parser.parse_changes(&changes);
assert!(result.contains(&"lodash".to_string()));
}
#[test]
fn test_default_parser_is_npm() {
let parser = NodeLockDiffParser::default();
assert!(matches!(parser.manager, NodePackageManager::Npm));
}
}