use super::default_version;
use crate::agent::Agent;
use crate::dependency::{self, DependencyKind, DependencyTree};
use crate::package::Package;
use crate::package::manifest::{Handler, Manifest, ManifestBox};
use crate::version::ComparatorExt;
use anyhow::Result;
use itertools::Itertools;
use semver::{Comparator, Version};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Deserialize)]
#[serde(rename_all(deserialize = "camelCase"))]
pub(super) struct PackageJson {
#[serde(default)]
name: String,
#[serde(default = "default_version")]
version: String,
#[serde(default)]
package_manager: Option<String>,
#[serde(default)]
dependencies: HashMap<String, String>,
#[serde(default)]
dev_dependencies: HashMap<String, String>,
#[serde(default)]
peer_dependencies: HashMap<String, String>,
}
impl Manifest for PackageJson {
type Value = serde_json::Value;
fn read(path: impl AsRef<Path>) -> Result<ManifestBox> {
let contents = fs::read_to_string(path)?;
let manifest: PackageJson = serde_json::from_str(&contents)?;
Ok(Box::new(manifest))
}
fn read_as_value(path: impl AsRef<Path>) -> Result<Self::Value> {
let contents = fs::read_to_string(path)?;
Ok(serde_json::from_str::<Self::Value>(&contents)?)
}
}
impl Handler for PackageJson {
fn agent(&self) -> Agent {
match &self.package_manager {
Some(pm) if pm.starts_with("pnpm") => Agent::Pnpm,
_ => Agent::Npm,
}
}
fn bump(&self, package: &Package, version: Version) -> Result<()> {
let mut manifest = PackageJson::read_as_value(&package.path)?;
manifest["version"] = Value::String(version.to_string());
let mut contents = serde_json::to_string_pretty(&manifest)?;
if !contents.ends_with('\n') {
contents.push('\n');
}
fs::write(&package.path, contents)?;
Ok(())
}
fn dependency_tree(&self) -> DependencyTree {
let mut tree = DependencyTree::new(self.agent());
macro_rules! add {
($deps:expr, $kind:ident) => {
if !$deps.is_empty() {
tree.add_many($deps, DependencyKind::$kind);
}
};
}
add!(&self.dependencies, Normal);
add!(&self.dev_dependencies, Development);
add!(&self.peer_dependencies, Peer);
if let Some(pm) = &self.package_manager
&& let Some((name, version)) = pm.split('@').next_tuple()
&& let Ok(comparator) = Comparator::parse(version)
{
tree.add(name, comparator, DependencyKind::PackageManager);
}
tree
}
fn name(&self) -> &str {
self.name.as_str()
}
fn update(&self, package: &Package, targets: &[dependency::Target]) -> Result<()> {
let mut manifest = PackageJson::read_as_value(&package.path)?;
for target in targets {
let key = match target.dependency.kind {
DependencyKind::Normal => "dependencies",
DependencyKind::Development => "devDependencies",
DependencyKind::Peer => "peerDependencies",
DependencyKind::PackageManager => "packageManager",
DependencyKind::Build => continue,
};
if target.dependency.kind.is_package_manager() {
let agent = package.agent().to_string().to_lowercase();
let version = target.comparator.as_version()?;
manifest[key] = Value::String(format!("{agent}@{version}"));
} else if let Some(deps) = manifest
.get_mut(key)
.and_then(Value::as_object_mut)
{
let comparator = Value::String(target.comparator.to_string());
deps.insert(target.dependency.name.clone(), comparator);
}
}
let mut contents = serde_json::to_string_pretty(&manifest)?;
if !contents.ends_with('\n') {
contents.push('\n');
}
fs::write(&package.path, contents)?;
Ok(())
}
fn version(&self) -> Result<Version> {
Version::parse(&self.version).map_err(Into::into)
}
}