docs: add package docs

This commit is contained in:
daimond113 2024-08-03 22:18:38 +02:00
parent e07ec4e859
commit 431c2b634f
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
22 changed files with 345 additions and 17 deletions

View file

@ -29,19 +29,21 @@ impl SelfInstallCommand {
.0;
let path: String = env.get_value("Path").context("failed to get Path value")?;
let bin_dir = bin_dir.to_string_lossy();
let exists = path
.split(';')
.any(|part| part == bin_dir.to_string_lossy().as_ref());
.any(|part| *part == bin_dir);
if !exists {
let new_path = format!("{path};{}", bin_dir.to_string_lossy());
let new_path = format!("{path};{bin_dir}");
env.set_value("Path", &new_path)
.context("failed to set Path value")?;
}
println!(
"installed {} {}!",
env!("CARGO_PKG_NAME").cyan(),
env!("CARGO_BIN_NAME").cyan(),
env!("CARGO_PKG_VERSION").yellow(),
);
@ -59,13 +61,13 @@ impl SelfInstallCommand {
#[cfg(unix)]
{
println!(
r#"installed {} {}! in order to be able to run binary exports as programs, add the following line to your shell profile:
r#"installed {} {}! add the following line to your shell profile in order to get the binary and binary exports as executables usable from anywhere:
{}
and then restart your shell.
"#,
env!("CARGO_PKG_NAME").cyan(),
env!("CARGO_BIN_NAME").cyan(),
env!("CARGO_PKG_VERSION").yellow(),
format!(r#"export PATH="$PATH:~/{}/bin""#, HOME_DIR)
.bold()

View file

@ -20,6 +20,7 @@ type MultithreadDownloadJob = (
);
impl Project {
/// Downloads a graph of dependencies
pub fn download_graph(
&self,
graph: &DependencyGraph,
@ -101,24 +102,31 @@ impl Project {
}
}
/// Errors that can occur when downloading a graph
pub mod errors {
use thiserror::Error;
/// Errors that can occur when downloading a graph
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DownloadGraphError {
/// Error occurred deserializing the project manifest
#[error("error deserializing project manifest")]
ManifestDeserializationFailed(#[from] crate::errors::ManifestReadError),
/// Error occurred refreshing a package source
#[error("failed to refresh package source")]
RefreshFailed(#[from] Box<crate::source::errors::RefreshError>),
/// Error interacting with the filesystem
#[error("error interacting with filesystem")]
Io(#[from] std::io::Error),
/// Error downloading a package
#[error("failed to download package")]
DownloadFailed(#[from] crate::source::errors::DownloadError),
/// Error writing package contents
#[error("failed to write package contents")]
WriteFailed(std::io::Error),
}

View file

@ -1,4 +1,7 @@
// #![deny(missing_docs)] - TODO: bring this back before publishing 0.5
#![deny(missing_docs)]
//! pesde is a package manager for Luau, designed to be feature-rich and easy to use.
//! pesde has its own registry, however it can also use Wally, and GitHub as package sources.
//! It has been designed with multiple targets in mind, namely Roblox, Lune, and Luau.
#[cfg(not(any(feature = "roblox", feature = "lune", feature = "luau")))]
compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled");
@ -6,24 +9,39 @@ compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be
use crate::lockfile::Lockfile;
use std::path::{Path, PathBuf};
/// Downloading packages
pub mod download;
/// Linking packages
pub mod linking;
/// Lockfile
pub mod lockfile;
/// Manifest
pub mod manifest;
/// Package names
pub mod names;
/// Patching packages
#[cfg(feature = "patches")]
pub mod patches;
/// Resolving packages
pub mod resolver;
/// Running scripts
pub mod scripts;
/// Package sources
pub mod source;
pub(crate) mod util;
/// The name of the manifest file
pub const MANIFEST_FILE_NAME: &str = "pesde.toml";
/// The name of the lockfile
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";
/// The name of the default index
pub const DEFAULT_INDEX_NAME: &str = "default";
/// The name of the packages container
pub const PACKAGES_CONTAINER_NAME: &str = ".pesde";
/// Maximum size of a package's archive
pub const MAX_ARCHIVE_SIZE: usize = 4 * 1024 * 1024;
/// Struct containing the authentication configuration
#[derive(Debug, Default, Clone)]
pub struct AuthConfig {
pesde_token: Option<String>,
@ -31,23 +49,28 @@ pub struct AuthConfig {
}
impl AuthConfig {
/// Create a new `AuthConfig`
pub fn new() -> Self {
AuthConfig::default()
}
/// Access the pesde token
pub fn pesde_token(&self) -> Option<&str> {
self.pesde_token.as_deref()
}
/// Access the git credentials
pub fn git_credentials(&self) -> Option<&gix::sec::identity::Account> {
self.git_credentials.as_ref()
}
/// Set the pesde token
pub fn with_pesde_token<S: AsRef<str>>(mut self, token: Option<S>) -> Self {
self.pesde_token = token.map(|s| s.as_ref().to_string());
self
}
/// Set the git credentials
pub fn with_git_credentials(
mut self,
git_credentials: Option<gix::sec::identity::Account>,
@ -57,6 +80,7 @@ impl AuthConfig {
}
}
/// The main struct of the pesde library, representing a project
#[derive(Debug, Clone)]
pub struct Project {
path: PathBuf,
@ -66,6 +90,7 @@ pub struct Project {
}
impl Project {
/// Create a new `Project`
pub fn new<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>>(
path: P,
data_dir: Q,
@ -80,41 +105,50 @@ impl Project {
}
}
/// Access the path
pub fn path(&self) -> &Path {
&self.path
}
/// Access the data directory
pub fn data_dir(&self) -> &Path {
&self.data_dir
}
/// Access the authentication configuration
pub fn auth_config(&self) -> &AuthConfig {
&self.auth_config
}
/// Access the CAS (content-addressable storage) directory
pub fn cas_dir(&self) -> &Path {
&self.cas_dir
}
/// Read the manifest file
pub fn read_manifest(&self) -> Result<String, errors::ManifestReadError> {
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
Ok(string)
}
/// Deserialize the manifest file
pub fn deser_manifest(&self) -> Result<manifest::Manifest, errors::ManifestReadError> {
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
Ok(toml::from_str(&string)?)
}
/// Write the manifest file
pub fn write_manifest<S: AsRef<[u8]>>(&self, manifest: S) -> Result<(), std::io::Error> {
std::fs::write(self.path.join(MANIFEST_FILE_NAME), manifest.as_ref())
}
/// Deserialize the lockfile
pub fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
let string = std::fs::read_to_string(self.path.join(LOCKFILE_FILE_NAME))?;
Ok(toml::from_str(&string)?)
}
/// Write the lockfile
pub fn write_lockfile(&self, lockfile: Lockfile) -> Result<(), errors::LockfileWriteError> {
let string = toml::to_string(&lockfile)?;
std::fs::write(self.path.join(LOCKFILE_FILE_NAME), string)?;
@ -122,35 +156,45 @@ impl Project {
}
}
/// Errors that can occur when reading or writing files
pub mod errors {
use thiserror::Error;
/// Errors that can occur when reading the manifest file
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ManifestReadError {
/// An IO error occurred
#[error("io error reading manifest file")]
Io(#[from] std::io::Error),
/// An error occurred while deserializing the manifest file
#[error("error deserializing manifest file")]
Serde(#[from] toml::de::Error),
}
/// Errors that can occur when reading the lockfile
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LockfileReadError {
/// An IO error occurred
#[error("io error reading lockfile")]
Io(#[from] std::io::Error),
/// An error occurred while deserializing the lockfile
#[error("error deserializing lockfile")]
Serde(#[from] toml::de::Error),
}
/// Errors that can occur when writing the lockfile
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LockfileWriteError {
/// An IO error occurred
#[error("io error writing lockfile")]
Io(#[from] std::io::Error),
/// An error occurred while serializing the lockfile
#[error("error serializing lockfile")]
Serde(#[from] toml::ser::Error),
}

View file

@ -41,6 +41,7 @@ impl Visitor for TypeVisitor {
}
}
/// Get the types exported by a file
pub fn get_file_types(file: &str) -> Result<Vec<String>, Vec<full_moon::Error>> {
let ast = full_moon::parse(file)?;
let mut visitor = TypeVisitor { types: vec![] };
@ -49,6 +50,7 @@ pub fn get_file_types(file: &str) -> Result<Vec<String>, Vec<full_moon::Error>>
Ok(visitor.types)
}
/// Generate a linking module for a library
pub fn generate_lib_linking_module<I: IntoIterator<Item = S>, S: AsRef<str>>(
path: &str,
types: I,
@ -80,6 +82,7 @@ fn luau_style_path(path: &Path) -> String {
format!("{require:?}")
}
/// Get the require path for a library
pub fn get_lib_require_path(
target: &TargetKind,
base_dir: &Path,
@ -121,10 +124,12 @@ pub fn get_lib_require_path(
luau_style_path(&path)
}
/// Generate a linking module for a binary
pub fn generate_bin_linking_module(path: &str) -> String {
format!("return require({path})")
}
/// Get the require path for a binary
pub fn get_bin_require_path(
base_dir: &Path,
bin_file: &RelativePathBuf,

View file

@ -13,6 +13,7 @@ use std::{
path::{Path, PathBuf},
};
/// Generates linking modules for a project
pub mod generator;
fn create_and_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
@ -28,6 +29,7 @@ fn write_cas(destination: PathBuf, cas_dir: &Path, contents: &str) -> std::io::R
}
impl Project {
/// Links the dependencies of the project
pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> {
let manifest = self.deser_manifest()?;
@ -207,27 +209,35 @@ impl Project {
}
}
/// Errors that can occur while linking dependencies
pub mod errors {
use thiserror::Error;
/// Errors that can occur while linking dependencies
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LinkingError {
/// An error occurred while deserializing the project manifest
#[error("error deserializing project manifest")]
Manifest(#[from] crate::errors::ManifestReadError),
/// An error occurred while interacting with the filesystem
#[error("error interacting with filesystem")]
Io(#[from] std::io::Error),
/// A dependency was not found
#[error("dependency not found: {0}@{1}")]
DependencyNotFound(String, String),
/// The library file was not found
#[error("library file at {0} not found")]
LibFileNotFound(String),
/// An error occurred while parsing a Luau script
#[error("error parsing Luau script at {0}")]
FullMoon(String, Vec<full_moon::Error>),
/// An error occurred while generating a Roblox sync config
#[cfg(feature = "roblox")]
#[error("error generating roblox sync config for {0}")]
GenerateRobloxSyncConfig(String, #[source] std::io::Error),

View file

@ -17,19 +17,26 @@ use std::{
path::{Path, PathBuf},
};
/// A graph of dependencies
pub type Graph<Node> = BTreeMap<PackageNames, BTreeMap<VersionId, Node>>;
/// A dependency graph node
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DependencyGraphNode {
/// The alias and specifiers for the dependency, if it is a direct dependency (i.e. used by the current project)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direct: Option<(String, DependencySpecifiers)>,
/// The dependencies of the package
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<PackageNames, (VersionId, String)>,
/// The type of the dependency
pub ty: DependencyType,
/// The package reference
pub pkg_ref: PackageRefs,
}
impl DependencyGraphNode {
/// Returns the folder to store dependencies in for this package
pub fn base_folder(&self, project_target: TargetKind, is_top_level: bool) -> String {
if is_top_level || self.pkg_ref.use_new_structure() {
project_target.packages_folder(&self.pkg_ref.target_kind())
@ -38,6 +45,7 @@ impl DependencyGraphNode {
}
}
/// Returns the folder to store the contents of the package in
pub fn container_folder<P: AsRef<Path>>(
&self,
path: &P,
@ -51,8 +59,10 @@ impl DependencyGraphNode {
}
}
/// A graph of `DependencyGraphNode`s
pub type DependencyGraph = Graph<DependencyGraphNode>;
/// Inserts a node into a graph
pub fn insert_node(
graph: &mut DependencyGraph,
name: PackageNames,
@ -92,23 +102,33 @@ pub fn insert_node(
}
}
/// A downloaded dependency graph node, i.e. a `DependencyGraphNode` with a `Target`
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DownloadedDependencyGraphNode {
/// The target of the package
pub target: Target,
/// The node
#[serde(flatten)]
pub node: DependencyGraphNode,
}
/// A graph of `DownloadedDependencyGraphNode`s
pub type DownloadedGraph = Graph<DownloadedDependencyGraphNode>;
/// A lockfile
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lockfile {
/// The name of the package
pub name: PackageName,
/// The version of the package
pub version: Version,
/// The target of the package
pub target: TargetKind,
/// The overrides of the package
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
/// The graph of dependencies
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub graph: DownloadedGraph,
}

View file

@ -68,7 +68,7 @@ fn run() -> anyhow::Result<()> {
parent.as_os_str() != "bin"
|| parent
.parent()
.is_some_and(|parent| parent.as_os_str() != cli::HOME_DIR)
.is_some_and(|parent| parent.as_os_str() != HOME_DIR)
}) {
break 'scripts;
}

View file

@ -10,32 +10,46 @@ use crate::{
source::specifiers::DependencySpecifiers,
};
/// Overrides
pub mod overrides;
/// Targets
pub mod target;
/// A package manifest
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Manifest {
/// The name of the package
pub name: PackageName,
/// The version of the package
pub version: Version,
/// The description of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// The license of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
/// The authors of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authors: Option<Vec<String>>,
/// The repository of the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
/// The target of the package
pub target: Target,
/// Whether the package is private
#[serde(default)]
pub private: bool,
/// The scripts of the package
#[serde(default, skip_serializing)]
pub scripts: BTreeMap<String, RelativePathBuf>,
/// The indices to use for the package
#[serde(
default,
serialize_with = "crate::util::serialize_gix_url_map",
deserialize_with = "crate::util::deserialize_gix_url_map"
)]
pub indices: BTreeMap<String, gix::Url>,
/// The indices to use for the package's wally dependencies
#[cfg(feature = "wally-compat")]
#[serde(
default,
@ -44,10 +58,13 @@ pub struct Manifest {
deserialize_with = "crate::util::deserialize_gix_url_map"
)]
pub wally_indices: BTreeMap<String, gix::Url>,
/// The overrides this package has
#[serde(default, skip_serializing)]
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
/// The files to include in the package
#[serde(default)]
pub includes: BTreeSet<String>,
/// The patches to apply to packages
#[cfg(feature = "patches")]
#[serde(default, skip_serializing)]
pub patches: BTreeMap<
@ -55,25 +72,34 @@ pub struct Manifest {
BTreeMap<crate::source::version_id::VersionId, RelativePathBuf>,
>,
#[serde(default, skip_serializing)]
/// Which version of the pesde CLI this package uses
pub pesde_version: Option<Version>,
/// The standard dependencies of the package
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<String, DependencySpecifiers>,
/// The peer dependencies of the package
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub peer_dependencies: BTreeMap<String, DependencySpecifiers>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
/// The dev dependencies of the package
#[serde(default, skip_serializing)]
pub dev_dependencies: BTreeMap<String, DependencySpecifiers>,
}
/// A dependency type
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum DependencyType {
/// A standard dependency
Standard,
Dev,
/// A peer dependency
Peer,
/// A dev dependency
Dev,
}
impl Manifest {
/// Get all dependencies from the manifest
pub fn all_dependencies(
&self,
) -> Result<
@ -98,12 +124,15 @@ impl Manifest {
}
}
/// Errors that can occur when interacting with manifests
pub mod errors {
use thiserror::Error;
/// Errors that can occur when trying to get all dependencies from a manifest
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AllDependenciesError {
/// Another specifier is already using the alias
#[error("another specifier is already using the alias {0}")]
AliasConflict(String),
}

View file

@ -4,6 +4,7 @@ use std::{
str::FromStr,
};
/// An override key
#[derive(
Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
@ -46,12 +47,15 @@ impl Display for OverrideKey {
}
}
/// Errors that can occur when interacting with override keys
pub mod errors {
use thiserror::Error;
/// Errors that can occur when parsing an override key
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum OverrideKeyFromStr {
/// The override key is empty
#[error("empty override key")]
Empty,
}

View file

@ -6,13 +6,17 @@ use std::{
str::FromStr,
};
/// A kind of target
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum TargetKind {
/// A Roblox target
#[cfg(feature = "roblox")]
Roblox,
/// A Lune target
#[cfg(feature = "lune")]
Lune,
/// A Luau target
#[cfg(feature = "luau")]
Luau,
}
@ -47,6 +51,7 @@ impl FromStr for TargetKind {
}
impl TargetKind {
/// All possible target variants
pub const VARIANTS: &'static [TargetKind] = &[
#[cfg(feature = "roblox")]
TargetKind::Roblox,
@ -56,7 +61,8 @@ impl TargetKind {
TargetKind::Luau,
];
// self is the project's target, dependency is the target of the dependency
/// Whether this target is compatible with another target
/// self is the project's target, dependency is the target of the dependency
pub fn is_compatible_with(&self, dependency: &Self) -> bool {
if self == dependency {
return true;
@ -70,6 +76,8 @@ impl TargetKind {
}
}
/// The folder to store packages in for this target
/// self is the project's target, dependency is the target of the dependency
pub fn packages_folder(&self, dependency: &Self) -> String {
if self == dependency {
return "packages".to_string();
@ -79,33 +87,44 @@ impl TargetKind {
}
}
/// A target of a package
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case", tag = "environment")]
pub enum Target {
/// A Roblox target
#[cfg(feature = "roblox")]
Roblox {
/// The path to the lib export file
#[serde(default)]
lib: Option<RelativePathBuf>,
/// The files to include in the sync tool's config
#[serde(default)]
build_files: BTreeSet<String>,
},
/// A Lune target
#[cfg(feature = "lune")]
Lune {
/// The path to the lib export file
#[serde(default)]
lib: Option<RelativePathBuf>,
/// The path to the bin export file
#[serde(default)]
bin: Option<RelativePathBuf>,
},
/// A Luau target
#[cfg(feature = "luau")]
Luau {
/// The path to the lib export file
#[serde(default)]
lib: Option<RelativePathBuf>,
/// The path to the bin export file
#[serde(default)]
bin: Option<RelativePathBuf>,
},
}
impl Target {
/// Returns the kind of this target
pub fn kind(&self) -> TargetKind {
match self {
#[cfg(feature = "roblox")]
@ -117,6 +136,7 @@ impl Target {
}
}
/// Returns the path to the lib export file
pub fn lib_path(&self) -> Option<&RelativePathBuf> {
match self {
#[cfg(feature = "roblox")]
@ -128,6 +148,7 @@ impl Target {
}
}
/// Returns the path to the bin export file
pub fn bin_path(&self) -> Option<&RelativePathBuf> {
match self {
#[cfg(feature = "roblox")]
@ -139,6 +160,7 @@ impl Target {
}
}
/// Validates the target for publishing
pub fn validate_publish(&self) -> Result<(), errors::TargetValidatePublishError> {
let has_exports = match self {
#[cfg(feature = "roblox")]
@ -170,23 +192,29 @@ impl Display for Target {
}
}
/// Errors that can occur when working with targets
pub mod errors {
use thiserror::Error;
/// Errors that can occur when validating a target for publishing
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TargetValidatePublishError {
/// No exported files specified
#[error("no exported files specified")]
NoExportedFiles,
/// Roblox target must have at least one build file
#[cfg(feature = "roblox")]
#[error("roblox target must have at least one build file")]
NoBuildFiles,
}
/// Errors that can occur when parsing a target kind from a string
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TargetKindFromStr {
/// The target kind is unknown
#[error("unknown target kind {0}")]
Unknown(String),
}

View file

@ -2,9 +2,12 @@ use std::{fmt::Display, str::FromStr};
use serde_with::{DeserializeFromStr, SerializeDisplay};
/// The invalid part of a package name
#[derive(Debug)]
pub enum ErrorReason {
/// The scope of the package name is invalid
Scope,
/// The name of the package name is invalid
Name,
}
@ -17,6 +20,7 @@ impl Display for ErrorReason {
}
}
/// A pesde package name
#[derive(
Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
@ -59,29 +63,35 @@ impl Display for PackageName {
}
impl PackageName {
/// Returns the parts of the package name
pub fn as_str(&self) -> (&str, &str) {
(&self.0, &self.1)
}
/// Returns the package name as a string suitable for use in the filesystem
pub fn escaped(&self) -> String {
format!("{}+{}", self.0, self.1)
}
}
/// All possible package names
#[derive(
Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, PartialOrd, Ord,
)]
pub enum PackageNames {
/// A pesde package name
Pesde(PackageName),
}
impl PackageNames {
/// Returns the parts of the package name
pub fn as_str(&self) -> (&str, &str) {
match self {
PackageNames::Pesde(name) => name.as_str(),
}
}
/// Returns the package name as a string suitable for use in the filesystem
pub fn escaped(&self) -> String {
match self {
PackageNames::Pesde(name) => name.escaped(),
@ -109,32 +119,41 @@ impl FromStr for PackageNames {
}
}
/// Errors that can occur when working with package names
pub mod errors {
use thiserror::Error;
use crate::names::ErrorReason;
/// Errors that can occur when working with pesde package names
#[derive(Debug, Error)]
pub enum PackageNameError {
/// The package name is not in the format `scope/name`
#[error("package name `{0}` is not in the format `scope/name`")]
InvalidFormat(String),
/// The package name is outside the allowed characters: a-z, 0-9, and _
#[error("package {0} `{1}` contains characters outside a-z, 0-9, and _")]
InvalidCharacters(ErrorReason, String),
/// The package name contains only digits
#[error("package {0} `{1}` contains only digits")]
OnlyDigits(ErrorReason, String),
/// The package name starts or ends with an underscore
#[error("package {0} `{1}` starts or ends with an underscore")]
PrePostfixUnderscore(ErrorReason, String),
/// The package name is not within 3-32 characters long
#[error("package {0} `{1}` is not within 3-32 characters long")]
InvalidLength(ErrorReason, String),
}
/// Errors that can occur when working with package names
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PackageNamesError {
/// The pesde package name is invalid
#[error("invalid package name {0}")]
InvalidPackageName(String),
}

View file

@ -3,6 +3,7 @@ use git2::{ApplyLocation, ApplyOptions, Diff, DiffFormat, DiffLineType, Reposito
use relative_path::RelativePathBuf;
use std::{fs::read, path::Path};
/// Set up a git repository for patches
pub fn setup_patches_repo<P: AsRef<Path>>(dir: P) -> Result<Repository, git2::Error> {
let repo = Repository::init(&dir)?;
@ -31,6 +32,7 @@ pub fn setup_patches_repo<P: AsRef<Path>>(dir: P) -> Result<Repository, git2::Er
Ok(repo)
}
/// Create a patch from the current state of the repository
pub fn create_patch<P: AsRef<Path>>(dir: P) -> Result<Vec<u8>, git2::Error> {
let mut patches = vec![];
let repo = Repository::open(dir.as_ref())?;
@ -65,6 +67,7 @@ pub fn create_patch<P: AsRef<Path>>(dir: P) -> Result<Vec<u8>, git2::Error> {
}
impl Project {
/// Apply patches to the project's dependencies
pub fn apply_patches(&self, graph: &DownloadedGraph) -> Result<(), errors::ApplyPatchesError> {
let manifest = self.deser_manifest()?;
@ -140,27 +143,34 @@ impl Project {
}
}
/// Errors that can occur when using patches
pub mod errors {
use std::path::PathBuf;
use crate::{names::PackageNames, source::version_id::VersionId};
use thiserror::Error;
/// Errors that can occur when applying patches
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ApplyPatchesError {
/// Error deserializing the project manifest
#[error("error deserializing project manifest")]
ManifestDeserializationFailed(#[from] crate::errors::ManifestReadError),
/// Error interacting with git
#[error("error interacting with git")]
GitError(#[from] git2::Error),
/// Error reading the patch file
#[error("error reading patch file at {0}")]
PatchReadError(PathBuf, #[source] std::io::Error),
/// Error removing the .git directory
#[error("error removing .git directory")]
GitDirectoryRemovalError(PathBuf, #[source] std::io::Error),
/// Package not found in the graph
#[error("package {0}@{1} not found in graph")]
PackageNotFound(PackageNames, VersionId),
}

View file

@ -14,6 +14,7 @@ use crate::{
use std::collections::{HashMap, HashSet, VecDeque};
impl Project {
/// Create a dependency graph from the project's manifest
pub fn dependency_graph(
&self,
previous_graph: Option<&DependencyGraph>,
@ -293,27 +294,35 @@ impl Project {
}
}
/// Errors that can occur when resolving dependencies
pub mod errors {
use thiserror::Error;
/// Errors that can occur when creating a dependency graph
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DependencyGraphError {
/// An error occurred while deserializing the manifest
#[error("failed to deserialize manifest")]
ManifestRead(#[from] crate::errors::ManifestReadError),
/// An error occurred while reading all dependencies from the manifest
#[error("error getting all project dependencies")]
AllDependencies(#[from] crate::manifest::errors::AllDependenciesError),
/// An index was not found in the manifest
#[error("index named {0} not found in manifest")]
IndexNotFound(String),
/// An error occurred while refreshing a package source
#[error("error refreshing package source")]
Refresh(#[from] crate::source::errors::RefreshError),
/// An error occurred while resolving a package
#[error("error resolving package")]
Resolve(#[from] crate::source::errors::ResolveError),
/// No matching version was found for a specifier
#[error("no matching version found for {0}")]
NoMatchingVersion(String),
}

View file

@ -8,10 +8,13 @@ use std::{
use std::fmt::{Display, Formatter};
/// Script names used by pesde
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ScriptName {
/// Generates a config for syncing tools for Roblox. For example, for Rojo it should create a `default.project.json` file
#[cfg(feature = "roblox")]
RobloxSyncConfigGenerator,
/// Prints a sourcemap for a Wally package, used for finding the library export file
#[cfg(feature = "wally-compat")]
SourcemapGenerator,
}
@ -27,6 +30,7 @@ impl Display for ScriptName {
}
}
/// Executes a script with the given arguments
pub fn execute_script<A: IntoIterator<Item = S>, S: AsRef<OsStr>, P: AsRef<Path>>(
script_name: Option<&str>,
script_path: &Path,

View file

@ -6,14 +6,18 @@ use std::{
path::{Path, PathBuf},
};
/// A file system entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FSEntry {
/// A file with the given hash
#[serde(rename = "f")]
File(String),
/// A directory
#[serde(rename = "d")]
Directory,
}
/// A package's file system
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PackageFS(pub(crate) BTreeMap<RelativePathBuf, FSEntry>);
@ -37,6 +41,7 @@ pub(crate) fn store_in_cas<P: AsRef<Path>>(
}
impl PackageFS {
/// Write the package to the given destination
pub fn write_to<P: AsRef<Path>, Q: AsRef<Path>>(
&self,
destination: P,

View file

@ -10,23 +10,32 @@ use crate::{
Project,
};
/// Packages' filesystems
pub mod fs;
/// The pesde package source
pub mod pesde;
/// Package references
pub mod refs;
/// Dependency specifiers
pub mod specifiers;
/// Traits for sources and packages
pub mod traits;
/// Version IDs
pub mod version_id;
/// The result of resolving a package
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
/// All possible package sources
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum PackageSources {
/// A pesde package source
Pesde(pesde::PesdePackageSource),
}
impl PackageSource for PackageSources {
type Ref = PackageRefs;
type Specifier = DependencySpecifiers;
type Ref = PackageRefs;
type RefreshError = errors::RefreshError;
type ResolveError = errors::ResolveError;
type DownloadError = errors::DownloadError;
@ -77,32 +86,41 @@ impl PackageSource for PackageSources {
}
}
/// Errors that can occur when interacting with a package source
pub mod errors {
use thiserror::Error;
/// Errors that occur when refreshing a package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RefreshError {
/// The pesde package source failed to refresh
#[error("error refreshing pesde package source")]
Pesde(#[from] crate::source::pesde::errors::RefreshError),
}
/// Errors that can occur when resolving a package
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ResolveError {
/// The dependency specifier does not match the source (if using the CLI, this is a bug - file an issue)
#[error("mismatched dependency specifier for source")]
Mismatch,
/// The pesde package source failed to resolve
#[error("error resolving pesde package")]
Pesde(#[from] crate::source::pesde::errors::ResolveError),
}
/// Errors that can occur when downloading a package
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DownloadError {
/// The package ref does not match the source (if using the CLI, this is a bug - file an issue)
#[error("mismatched package ref for source")]
Mismatch,
/// The pesde package source failed to download
#[error("error downloading pesde package")]
Pesde(#[from] crate::source::pesde::errors::DownloadError),
}

View file

@ -25,22 +25,29 @@ use crate::{
Project,
};
/// The pesde package reference
pub mod pkg_ref;
/// The pesde dependency specifier
pub mod specifier;
/// The pesde package source
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct PesdePackageSource {
repo_url: gix::Url,
}
/// The file containing scope information
pub const SCOPE_INFO_FILE: &str = "scope.toml";
/// Information about a scope
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeInfo {
/// The people authorized to publish packages to this scope
pub owners: BTreeSet<u64>,
}
impl PesdePackageSource {
/// Creates a new pesde package source
pub fn new(repo_url: gix::Url) -> Self {
Self { repo_url }
}
@ -49,10 +56,12 @@ impl PesdePackageSource {
self.repo_url.to_bstring().to_vec()
}
/// The path to the index
pub fn path(&self, project: &Project) -> std::path::PathBuf {
project.data_dir.join("indices").join(hash(self.as_bytes()))
}
/// The URL of the repository
pub fn repo_url(&self) -> &gix::Url {
&self.repo_url
}
@ -108,6 +117,7 @@ impl PesdePackageSource {
}
}
/// Reads a file from the index
pub fn read_file<
I: IntoIterator<Item = P> + Clone,
P: ToString + PartialEq<gix::bstr::BStr>,
@ -154,6 +164,7 @@ impl PesdePackageSource {
Ok(Some(string))
}
/// Reads the config file
pub fn config(&self, project: &Project) -> Result<IndexConfig, errors::ConfigError> {
let file = self.read_file(["config.toml"], project).map_err(Box::new)?;
@ -171,6 +182,7 @@ impl PesdePackageSource {
Ok(config)
}
/// Reads all packages from the index
pub fn all_packages(
&self,
project: &Project,
@ -253,6 +265,7 @@ impl PesdePackageSource {
Ok(packages)
}
/// The git2 repository for the index
#[cfg(feature = "git2")]
pub fn repo_git2(&self, project: &Project) -> Result<git2::Repository, git2::Error> {
let path = self.path(project);
@ -262,8 +275,8 @@ impl PesdePackageSource {
}
impl PackageSource for PesdePackageSource {
type Ref = PesdePackageRef;
type Specifier = PesdeDependencySpecifier;
type Ref = PesdePackageRef;
type RefreshError = errors::RefreshError;
type ResolveError = errors::ResolveError;
type DownloadError = errors::DownloadError;
@ -446,23 +459,31 @@ impl PackageSource for PesdePackageSource {
}
}
/// The configuration for the pesde index
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct IndexConfig {
/// The URL of the API
pub api: url::Url,
/// The URL to download packages from
pub download: Option<String>,
/// Whether git is allowed as a source for publishing packages
#[serde(default)]
pub git_allowed: bool,
/// Whether other registries are allowed as a source for publishing packages
#[serde(default)]
pub other_registries_allowed: bool,
/// The OAuth client ID for GitHub
pub github_oauth_client_id: String,
}
impl IndexConfig {
/// The URL of the API
pub fn api(&self) -> &str {
self.api.as_str().trim_end_matches('/')
}
/// The URL to download packages from
pub fn download(&self) -> String {
self.download
.as_deref()
@ -471,181 +492,239 @@ impl IndexConfig {
}
}
/// The entry in a package's index file
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct IndexFileEntry {
/// The target for this package
pub target: Target,
/// When this package was published
#[serde(default = "chrono::Utc::now")]
pub published_at: chrono::DateTime<chrono::Utc>,
/// The description of this package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// The license of this package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
/// The dependencies of this package
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
}
/// The index file for a package
pub type IndexFile = BTreeMap<VersionId, IndexFileEntry>;
/// Errors that can occur when interacting with the pesde package source
pub mod errors {
use std::path::PathBuf;
use thiserror::Error;
/// Errors that can occur when refreshing the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RefreshError {
/// Error interacting with the filesystem
#[error("error interacting with the filesystem")]
Io(#[from] std::io::Error),
/// Error opening the repository
#[error("error opening repository at {0}")]
Open(PathBuf, #[source] gix::open::Error),
/// No default remote found in repository
#[error("no default remote found in repository at {0}")]
NoDefaultRemote(PathBuf),
/// Error getting default remote from repository
#[error("error getting default remote from repository at {0}")]
GetDefaultRemote(PathBuf, #[source] gix::remote::find::existing::Error),
/// Error connecting to remote repository
#[error("error connecting to remote repository at {0}")]
Connect(gix::Url, #[source] gix::remote::connect::Error),
/// Error preparing fetch from remote repository
#[error("error preparing fetch from remote repository at {0}")]
PrepareFetch(gix::Url, #[source] gix::remote::fetch::prepare::Error),
/// Error reading from remote repository
#[error("error reading from remote repository at {0}")]
Read(gix::Url, #[source] gix::remote::fetch::Error),
/// Error cloning repository
#[error("error cloning repository from {0}")]
Clone(gix::Url, #[source] gix::clone::Error),
/// Error fetching repository
#[error("error fetching repository from {0}")]
Fetch(gix::Url, #[source] gix::clone::fetch::Error),
}
/// Errors that can occur when reading the pesde package source's tree
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TreeError {
/// Error interacting with the filesystem
#[error("error interacting with the filesystem")]
Io(#[from] std::io::Error),
/// No default remote found in repository
#[error("no default remote found in repository at {0}")]
NoDefaultRemote(PathBuf),
/// Error getting default remote from repository
#[error("error getting default remote from repository at {0}")]
GetDefaultRemote(PathBuf, #[source] Box<gix::remote::find::existing::Error>),
/// Error getting refspec from remote repository
#[error("no refspecs found in repository at {0}")]
NoRefSpecs(PathBuf),
/// Error getting local refspec from remote repository
#[error("no local refspec found in repository at {0}")]
NoLocalRefSpec(PathBuf),
/// Error finding reference in repository
#[error("no reference found for local refspec {0}")]
NoReference(String, #[source] gix::reference::find::existing::Error),
/// Error peeling reference in repository
#[error("cannot peel reference {0}")]
CannotPeel(String, #[source] gix::reference::peel::Error),
/// Error converting id to object in repository
#[error("error converting id {0} to object")]
CannotConvertToObject(String, #[source] gix::object::find::existing::Error),
/// Error peeling object to tree in repository
#[error("error peeling object {0} to tree")]
CannotPeelToTree(String, #[source] gix::object::peel::to_kind::Error),
}
/// Errors that can occur when reading a file from the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ReadFile {
/// Error opening the repository
#[error("error opening repository at {0}")]
Open(PathBuf, #[source] Box<gix::open::Error>),
/// Error reading tree from repository
#[error("error getting tree from repository at {0}")]
Tree(PathBuf, #[source] Box<TreeError>),
/// Error looking up entry in tree
#[error("error looking up entry {0} in tree")]
Lookup(String, #[source] gix::object::find::existing::Error),
/// Error reading file as utf8
#[error("error parsing file for {0} as utf8")]
Utf8(String, #[source] std::string::FromUtf8Error),
}
/// Errors that can occur when resolving a package from the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ResolveError {
/// Error interacting with the filesystem
#[error("error interacting with the filesystem")]
Io(#[from] std::io::Error),
/// Package not found in index
#[error("package {0} not found")]
NotFound(String),
/// Error reading file for package
#[error("error reading file for {0}")]
Read(String, #[source] Box<ReadFile>),
/// Error parsing file for package
#[error("error parsing file for {0}")]
Parse(String, #[source] toml::de::Error),
/// Error parsing file for package as utf8
#[error("error parsing file for {0} to utf8")]
Utf8(String, #[source] std::string::FromUtf8Error),
}
/// Errors that can occur when reading the config file for the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ConfigError {
/// Error reading file
#[error("error reading config file")]
ReadFile(#[from] Box<ReadFile>),
/// Error parsing config file
#[error("error parsing config file")]
Parse(#[from] toml::de::Error),
/// The config file is missing
#[error("missing config file for index at {0}")]
Missing(Box<gix::Url>),
}
/// Errors that can occur when reading all packages from the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AllPackagesError {
/// Error opening the repository
#[error("error opening repository at {0}")]
Open(PathBuf, #[source] Box<gix::open::Error>),
/// Error reading tree from repository
#[error("error getting tree from repository at {0}")]
Tree(PathBuf, #[source] Box<TreeError>),
/// Error decoding entry in repository
#[error("error decoding entry in repository at {0}")]
Decode(PathBuf, #[source] gix::objs::decode::Error),
/// Error converting entry in repository
#[error("error converting entry in repository at {0}")]
Convert(PathBuf, #[source] gix::object::find::existing::Error),
/// Error deserializing file in repository
#[error("error deserializing file {0} in repository at {1}")]
Deserialize(String, PathBuf, #[source] Box<toml::de::Error>),
/// Error parsing file in repository as utf8
#[error("error parsing file for {0} as utf8")]
Utf8(String, #[source] std::string::FromUtf8Error),
}
/// Errors that can occur when downloading a package from the pesde package source
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DownloadError {
/// Error reading index file
#[error("error reading config file")]
ReadFile(#[from] Box<ConfigError>),
/// Error downloading package
#[error("error downloading package")]
Download(#[from] reqwest::Error),
/// Error unpacking package
#[error("error unpacking package")]
Unpack(#[from] std::io::Error),
/// Error writing index file
#[error("error writing index file")]
WriteIndex(#[source] std::io::Error),
/// Error serializing index file
#[error("error serializing index file")]
SerializeIndex(#[from] toml::ser::Error),
/// Error deserializing index file
#[error("error deserializing index file")]
DeserializeIndex(#[from] toml::de::Error),
/// Error writing index file
#[error("error reading index file")]
ReadIndex(#[source] std::io::Error),
}

View file

@ -3,12 +3,17 @@ use semver::VersionReq;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
/// The specifier for a pesde dependency
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct PesdeDependencySpecifier {
/// The name of the package
pub name: PackageName,
/// The version requirement for the package
pub version: VersionReq,
/// The index to use for the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<String>,
/// The target to use for the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<TargetKind>,
}

View file

@ -5,9 +5,11 @@ use crate::{
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// All possible package references
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case", tag = "ref_ty")]
pub enum PackageRefs {
/// A pesde package reference
Pesde(pesde::pkg_ref::PesdePackageRef),
}

View file

@ -2,9 +2,11 @@ use crate::source::{pesde, traits::DependencySpecifier};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
/// All possible dependency specifiers
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum DependencySpecifiers {
/// A pesde dependency specifier
Pesde(pesde::specifier::PesdeDependencySpecifier),
}
impl DependencySpecifier for DependencySpecifiers {}

View file

@ -12,26 +12,40 @@ use crate::{
Project,
};
/// A specifier for a dependency
pub trait DependencySpecifier: Debug + Display {}
/// A reference to a package
pub trait PackageRef: Debug {
/// The dependencies of this package
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>;
/// Whether to use the new structure (`packages` folders inside the package's content folder) or the old structure (Wally-style, with linker files in the parent of the folder containing the package's contents)
fn use_new_structure(&self) -> bool;
/// The target of this package
fn target_kind(&self) -> TargetKind;
/// The source of this package
fn source(&self) -> PackageSources;
}
/// A source of packages
pub trait PackageSource: Debug {
type Ref: PackageRef;
/// The specifier type for this source
type Specifier: DependencySpecifier;
/// The reference type for this source
type Ref: PackageRef;
/// The error type for refreshing this source
type RefreshError: std::error::Error;
/// The error type for resolving a package from this source
type ResolveError: std::error::Error;
/// The error type for downloading a package from this source
type DownloadError: std::error::Error;
/// Refreshes the source
fn refresh(&self, _project: &Project) -> Result<(), Self::RefreshError> {
Ok(())
}
/// Resolves a specifier to a reference
fn resolve(
&self,
specifier: &Self::Specifier,
@ -39,6 +53,7 @@ pub trait PackageSource: Debug {
project_target: TargetKind,
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError>;
/// Downloads a package
fn download(
&self,
pkg_ref: &Self::Ref,

View file

@ -3,24 +3,29 @@ use semver::Version;
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{fmt::Display, str::FromStr};
/// A version ID, which is a combination of a version and a target
#[derive(
Debug, SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
pub struct VersionId(pub(crate) Version, pub(crate) TargetKind);
impl VersionId {
/// Creates a new version ID
pub fn new(version: Version, target: TargetKind) -> Self {
VersionId(version, target)
}
/// Access the version
pub fn version(&self) -> &Version {
&self.0
}
/// Access the target
pub fn target(&self) -> &TargetKind {
&self.1
}
/// Returns this version ID as a string that can be used in the filesystem
pub fn escaped(&self) -> String {
format!("{}+{}", self.0, self.1)
}
@ -47,18 +52,23 @@ impl FromStr for VersionId {
}
}
/// Errors that can occur when using a version ID
pub mod errors {
use thiserror::Error;
/// Errors that can occur when parsing a version ID
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum VersionIdParseError {
#[error("malformed entry key {0}")]
/// The version ID is malformed
#[error("malformed version id {0}")]
Malformed(String),
/// The version is malformed
#[error("malformed version")]
Version(#[from] semver::Error),
/// The target is malformed
#[error("malformed target")]
Target(#[from] crate::manifest::target::errors::TargetKindFromStr),
}