pesde 0.7.3

A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune
Documentation
use crate::cli::{
	style::{ADDED_STYLE, INFO_STYLE, REMOVED_STYLE, SUCCESS_STYLE},
	up_to_date_lockfile,
};
use anyhow::Context as _;
use clap::Args;
use pesde::{
	source::{
		specifiers::DependencySpecifiers,
		traits::{PackageRef as _, PackageSource as _, RefreshOptions, ResolveOptions},
	},
	Project, RefreshedSources,
};
use semver::VersionReq;
use tokio::task::JoinSet;

#[derive(Debug, Args)]
pub struct OutdatedCommand {
	/// Whether to check within version requirements
	#[arg(short, long)]
	strict: bool,
}

impl OutdatedCommand {
	pub async fn run(self, project: Project) -> anyhow::Result<()> {
		let graph = match up_to_date_lockfile(&project).await? {
			Some(file) => file.graph,
			None => {
				anyhow::bail!(
					"lockfile is out of sync, run `{} install` to update it",
					env!("CARGO_BIN_NAME")
				);
			}
		};

		let manifest = project
			.deser_manifest()
			.await
			.context("failed to read manifest")?;
		let manifest_target_kind = manifest.target.kind();

		let refreshed_sources = RefreshedSources::new();

		let mut tasks = graph
			.into_iter()
			.map(|(current_id, node)| {
				let project = project.clone();
				let refreshed_sources = refreshed_sources.clone();
				async move {
					let Some((alias, mut specifier, _)) = node.direct else {
						return Ok::<_, anyhow::Error>(None);
					};

					if specifier.is_local() || matches!(specifier, DependencySpecifiers::Git(_)) {
						return Ok(None);
					}

					let source = node.pkg_ref.source();
					refreshed_sources
						.refresh(
							&source,
							&RefreshOptions {
								project: project.clone(),
							},
						)
						.await?;

					if !self.strict {
						match &mut specifier {
							DependencySpecifiers::Pesde(spec) => {
								spec.version = VersionReq::STAR;
							}
							#[cfg(feature = "wally-compat")]
							DependencySpecifiers::Wally(spec) => {
								spec.version = VersionReq::STAR;
							}
							DependencySpecifiers::Git(_) => {}
							DependencySpecifiers::Workspace(_) => {}
							DependencySpecifiers::Path(_) => {}
						}
					}

					let new_id = source
						.resolve(
							&specifier,
							&ResolveOptions {
								project: project.clone(),
								target: manifest_target_kind,
								refreshed_sources: refreshed_sources.clone(),
								loose_target: false,
							},
						)
						.await
						.context("failed to resolve package versions")?
						.1
						.pop_last()
						.map(|(v_id, _)| v_id)
						.with_context(|| format!("no versions of {specifier} found"))?;

					Ok(Some((alias, current_id, new_id))
						.filter(|(_, current_id, new_id)| current_id.version_id() != new_id))
				}
			})
			.collect::<JoinSet<_>>();

		let mut all_up_to_date = true;

		while let Some(task) = tasks.join_next().await {
			let Some((alias, current_id, new_id)) = task.unwrap()? else {
				continue;
			};

			all_up_to_date = false;

			println!(
				"{} ({}) {}{}",
				current_id.name(),
				INFO_STYLE.apply_to(alias),
				REMOVED_STYLE.apply_to(current_id.version_id()),
				ADDED_STYLE.apply_to(new_id),
			);
		}

		if all_up_to_date {
			println!("{}", SUCCESS_STYLE.apply_to("all packages are up to date"));
		}

		Ok(())
	}
}