feat: 🚸 add wally conversion

This commit is contained in:
daimond113 2024-03-16 22:36:12 +01:00
parent 4b0aca4eb0
commit 9e1ffc41b6
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
10 changed files with 254 additions and 59 deletions

2
Cargo.lock generated
View file

@ -3275,6 +3275,7 @@ dependencies = [
"tempfile",
"thiserror",
"threadpool",
"toml",
]
[[package]]
@ -3287,7 +3288,6 @@ dependencies = [
"actix-multipart-derive",
"actix-web",
"actix-web-httpauth",
"chrono",
"dotenvy",
"flate2",
"git2",

View file

@ -19,34 +19,35 @@ required-features = ["bin"]
[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
serde_yaml = "0.9.32"
toml = "0.8.11"
git2 = "0.18.2"
semver = { version = "1.0.22", features = ["serde"] }
reqwest = { version = "0.11.24", default-features = false, features = ["rustls-tls", "blocking"] }
reqwest = { version = "0.11.26", default-features = false, features = ["rustls-tls", "blocking"] }
tar = "0.4.40"
flate2 = "1.0.28"
pathdiff = "0.2.1"
relative-path = { version = "1.9.2", features = ["serde"] }
log = "0.4.20"
thiserror = "1.0.57"
log = "0.4.21"
thiserror = "1.0.58"
threadpool = "1.8.1"
full_moon = { version = "0.19.0", features = ["stacker", "roblox"] }
# chrono-lc breaks because of https://github.com/chronotope/chrono/compare/v0.4.34...v0.4.35#diff-67de5678fb5c14378bbff7ecf7f8bfab17cc223c4726f8da3afca183a4e59543
chrono = { version = "=0.4.34", features = ["serde"] }
clap = { version = "4.5.1", features = ["derive"], optional = true }
clap = { version = "4.5.3", features = ["derive"], optional = true }
directories = { version = "5.0.1", optional = true }
keyring = { version = "2.3.2", optional = true }
anyhow = { version = "1.0.80", optional = true }
anyhow = { version = "1.0.81", optional = true }
ignore = { version = "0.4.22", optional = true }
pretty_env_logger = { version = "0.5.0", optional = true }
serde_json = { version = "1.0.114", optional = true }
lune = { version = "0.8.0", optional = true }
lune = { version = "0.8.2", optional = true }
futures-executor = { version = "0.3.30", optional = true }
indicatif = { version = "0.17.8", optional = true }
auth-git2 = { version = "0.5.3", optional = true }
auth-git2 = { version = "0.5.4", optional = true }
indicatif-log-bridge = { version = "0.2.2", optional = true }
inquire = { version = "0.7.0", optional = true }
inquire = { version = "0.7.1", optional = true }
[dev-dependencies]
tempfile = "3.10.1"

View file

@ -307,17 +307,21 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
anyhow::bail!("manifest already exists");
}
let default_name = params.cwd.file_name().unwrap().to_str().unwrap();
let default_name = params.cwd.file_name().and_then(|s| s.to_str());
let name = Text::new("What is the name of the package?")
.with_initial_value(default_name)
.with_validator(|name: &str| {
let mut name =
Text::new("What is the name of the package?").with_validator(|name: &str| {
Ok(match PackageName::from_str(name) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.into()),
})
})
.prompt()?;
});
if let Some(name_str) = default_name {
name = name.with_initial_value(name_str);
}
let name = name.prompt()?;
let path_style =
Select::new("What style of paths do you want to use?", vec!["roblox"]).prompt()?;
@ -438,13 +442,12 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
)))?
.json::<Value>()?
.as_array()
.unwrap()
.iter()
.map(|v| Version::parse(v.as_str().unwrap()))
.collect::<Result<Vec<Version>, semver::Error>>()?
.into_iter()
.max()
.unwrap();
.and_then(|a| a.last())
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<Version>().ok())
.ok_or(anyhow::anyhow!(
"failed to get latest version of {name}@{version}"
))?;
if latest_version > version {
println!(
@ -455,6 +458,9 @@ pub fn root_command(cmd: Command, params: CliParams) -> anyhow::Result<()> {
}
}
}
Command::Convert => {
Manifest::from_path_or_convert(&params.cwd)?;
}
_ => unreachable!(),
}

View file

@ -8,7 +8,7 @@ use thiserror::Error;
use crate::{
index::{remote_callbacks, Index},
manifest::{Manifest, ManifestReadError, Realm},
manifest::{Manifest, ManifestConvertError, Realm},
package_name::PackageName,
project::Project,
};
@ -53,7 +53,7 @@ pub enum GitDownloadError {
/// An error that occurred while reading the manifest of the git repository
#[error("error reading manifest")]
ManifestRead(#[from] ManifestReadError),
ManifestRead(#[from] ManifestConvertError),
}
impl GitDependencySpecifier {
@ -120,7 +120,7 @@ impl GitDependencySpecifier {
repo.reset(&obj, git2::ResetType::Hard, None)?;
Ok((
Manifest::from_path(dest)?,
Manifest::from_path_or_convert(dest)?,
repo_url.to_string(),
obj.id().to_string(),
))
@ -153,6 +153,8 @@ impl GitPackageRef {
repo.reset(&obj, git2::ResetType::Hard, None)?;
Manifest::from_path_or_convert(dest)?;
Ok(())
}
}

View file

@ -143,7 +143,7 @@ impl<I: Index> Project<I> {
&self,
map: &ResolvedVersionsMap,
) -> Result<MultithreadedJob<DownloadError>, InstallProjectError> {
let job = MultithreadedJob::new();
let (job, tx) = MultithreadedJob::new();
for (name, versions) in map.clone() {
for (version, resolved_package) in versions {
@ -163,7 +163,7 @@ impl<I: Index> Project<I> {
let project = self.clone();
job.execute(move || resolved_package.pkg_ref.download(&project, source));
job.execute(&tx, move || resolved_package.pkg_ref.download(&project, source));
}
}

View file

@ -43,7 +43,7 @@ pub trait Index: Send + Sync + Debug + Clone + 'static {
&mut self,
manifest: &Manifest,
uploader: &u64,
) -> Result<bool, CreatePackageVersionError>;
) -> Result<Option<IndexFileEntry>, CreatePackageVersionError>;
/// Gets the index's configuration
fn config(&self) -> Result<IndexConfig, ConfigError>;
@ -421,7 +421,7 @@ impl Index for GitIndex {
&mut self,
manifest: &Manifest,
uploader: &u64,
) -> Result<bool, CreatePackageVersionError> {
) -> Result<Option<IndexFileEntry>, CreatePackageVersionError> {
let scope = manifest.name.scope();
if let Some(owners) = self.scope_owners(scope)? {
@ -436,14 +436,15 @@ impl Index for GitIndex {
let mut file = if let Some(file) = self.package(&manifest.name)? {
if file.iter().any(|e| e.version == manifest.version) {
return Ok(false);
return Ok(None);
}
file
} else {
vec![]
BTreeSet::new()
};
file.push(manifest.clone().into());
let entry: IndexFileEntry = manifest.clone().into();
file.insert(entry.clone());
serde_yaml::to_writer(
std::fs::File::create(path.join(manifest.name.name()))?,
@ -451,7 +452,7 @@ impl Index for GitIndex {
)
.map_err(CreatePackageVersionError::FileSer)?;
Ok(true)
Ok(Some(entry))
}
fn config(&self) -> Result<IndexConfig, ConfigError> {
@ -515,7 +516,7 @@ impl IndexConfig {
}
/// An entry in the index file
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct IndexFileEntry {
/// The version of the package
pub version: Version,
@ -550,5 +551,17 @@ impl From<Manifest> for IndexFileEntry {
}
}
impl PartialOrd for IndexFileEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.version.cmp(&other.version))
}
}
impl Ord for IndexFileEntry {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.version.cmp(&other.version)
}
}
/// An index file
pub type IndexFile = Vec<IndexFileEntry>;
pub type IndexFile = BTreeSet<IndexFileEntry>;

View file

@ -106,6 +106,9 @@ pub enum Command {
/// Publishes the project to the registry
Publish,
/// Converts a `wally.toml` file to a `pesde.yaml` file
Convert,
/// Begins a new patch
Patch {
/// The package to patch

View file

@ -1,11 +1,14 @@
use std::fs::read_to_string;
use std::path::PathBuf;
use std::str::FromStr;
use std::{collections::BTreeMap, fmt::Display, fs::read};
use relative_path::RelativePathBuf;
use semver::Version;
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::dependencies::registry::RegistryDependencySpecifier;
use crate::{dependencies::DependencySpecifier, package_name::PackageName, MANIFEST_FILE_NAME};
/// The files exported by the package
@ -159,6 +162,42 @@ pub enum ManifestReadError {
ManifestDeser(#[source] serde_yaml::Error),
}
/// An error that occurred while converting the manifest
#[derive(Debug, Error)]
pub enum ManifestConvertError {
/// An error that occurred while reading the manifest
#[error("error reading the manifest")]
ManifestRead(#[from] ManifestReadError),
/// An error that occurred while converting the manifest
#[error("error converting the manifest")]
ManifestConvert(#[source] toml::de::Error),
/// The given path does not have a parent
#[error("the path {0} does not have a parent")]
NoParent(PathBuf),
/// An error that occurred while interacting with the file system
#[error("error interacting with the file system")]
Io(#[from] std::io::Error),
/// An error that occurred while making a package name from a string
#[error("error making a package name from a string")]
PackageName(#[from] crate::package_name::FromStrPackageNameParseError),
/// An error that occurred while writing the manifest
#[error("error writing the manifest")]
ManifestWrite(#[from] serde_yaml::Error),
/// An error that occurred while converting a dependency specifier's version
#[error("error converting a dependency specifier's version")]
Version(#[from] semver::Error),
/// The dependency specifier isn't in the format of `scope/name@version`
#[error("the dependency specifier {0} isn't in the format of `scope/name@version`")]
InvalidDependencySpecifier(String),
}
/// The type of dependency
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
@ -187,6 +226,144 @@ impl Manifest {
Ok(manifest)
}
/// Tries to read the manifest from the given path, and if it fails, tries converting the `wally.toml` and writes a `pesde.yaml` in the same directory
pub fn from_path_or_convert<P: AsRef<std::path::Path>>(
path: P,
) -> Result<Self, ManifestConvertError> {
let dir_path = if path.as_ref().file_name() == Some(MANIFEST_FILE_NAME.as_ref()) {
path.as_ref()
.parent()
.ok_or_else(|| ManifestConvertError::NoParent(path.as_ref().to_path_buf()))?
.to_path_buf()
} else {
path.as_ref().to_path_buf()
};
Self::from_path(path).or_else(|_| {
#[derive(Deserialize)]
struct WallyPackage {
name: String,
version: Version,
#[serde(default)]
realm: Option<String>,
#[serde(default)]
description: Option<String>,
#[serde(default)]
license: Option<String>,
#[serde(default)]
authors: Option<Vec<String>>,
#[serde(default)]
private: Option<bool>,
}
#[derive(Deserialize, Default)]
struct WallyPlace {
#[serde(default)]
shared_packages: Option<String>,
#[serde(default)]
server_packages: Option<String>,
}
#[derive(Deserialize)]
struct WallyDependencySpecifier(String);
impl TryFrom<WallyDependencySpecifier> for DependencySpecifier {
type Error = ManifestConvertError;
fn try_from(specifier: WallyDependencySpecifier) -> Result<Self, Self::Error> {
let (name, req) = specifier.0.split_once('@').ok_or_else(|| {
ManifestConvertError::InvalidDependencySpecifier(specifier.0.clone())
})?;
let name: PackageName = name.replace('-', "_").parse()?;
let req: VersionReq = req.parse()?;
Ok(DependencySpecifier::Registry(RegistryDependencySpecifier {
name,
version: req,
realm: None,
}))
}
}
#[derive(Deserialize)]
struct WallyManifest {
package: WallyPackage,
#[serde(default)]
place: WallyPlace,
#[serde(default)]
dependencies: BTreeMap<String, WallyDependencySpecifier>,
#[serde(default)]
server_dependencies: BTreeMap<String, WallyDependencySpecifier>,
#[serde(default)]
dev_dependencies: BTreeMap<String, WallyDependencySpecifier>,
}
let toml_path = dir_path.join("wally.toml");
let toml_contents = read_to_string(toml_path)?;
let wally_manifest: WallyManifest =
toml::from_str(&toml_contents).map_err(ManifestConvertError::ManifestConvert)?;
let mut place = BTreeMap::new();
if let Some(shared) = wally_manifest.place.shared_packages {
if !shared.is_empty() {
place.insert(Realm::Shared, shared);
}
}
if let Some(server) = wally_manifest.place.server_packages {
if !server.is_empty() {
place.insert(Realm::Server, server);
}
}
let manifest = Self {
name: wally_manifest.package.name.replace('-', "_").parse()?,
version: wally_manifest.package.version,
exports: Exports::default(),
path_style: PathStyle::Roblox { place },
private: wally_manifest.package.private.unwrap_or(false),
realm: wally_manifest
.package
.realm
.map(|r| r.parse().unwrap_or(Realm::Shared)),
dependencies: [
(wally_manifest.dependencies, Realm::Shared),
(wally_manifest.server_dependencies, Realm::Server),
(wally_manifest.dev_dependencies, Realm::Development),
]
.into_iter()
.flat_map(|(deps, realm)| {
deps.into_values()
.map(|specifier| {
specifier.try_into().map(|mut specifier| {
match specifier {
DependencySpecifier::Registry(ref mut specifier) => {
specifier.realm = Some(realm);
}
_ => unreachable!(),
}
specifier
})
})
.collect::<Vec<_>>()
})
.collect::<Result<_, _>>()?,
peer_dependencies: Vec::new(),
description: wally_manifest.package.description,
license: wally_manifest.package.license,
authors: wally_manifest.package.authors,
repository: None,
};
let manifest_path = dir_path.join(MANIFEST_FILE_NAME);
serde_yaml::to_writer(std::fs::File::create(manifest_path)?, &manifest)?;
Ok(manifest)
})
}
/// Returns all dependencies
pub fn dependencies(&self) -> Vec<(DependencySpecifier, DependencyType)> {
self.dependencies

View file

@ -4,27 +4,19 @@ use threadpool::ThreadPool;
/// A multithreaded job
pub struct MultithreadedJob<E: Send + Sync + 'static> {
progress: Receiver<Result<(), E>>,
sender: Sender<Result<(), E>>,
pool: ThreadPool,
}
impl<E: Send + Sync + 'static> Default for MultithreadedJob<E> {
fn default() -> Self {
let (tx, rx) = std::sync::mpsc::channel();
let pool = ThreadPool::new(6);
Self {
progress: rx,
pool,
sender: tx.clone(),
}
}
}
impl<E: Send + Sync + 'static> MultithreadedJob<E> {
/// Creates a new multithreaded job
pub fn new() -> Self {
Self::default()
pub fn new() -> (Self, Sender<Result<(), E>>) {
let (tx, rx) = std::sync::mpsc::channel();
let pool = ThreadPool::new(6);
(Self {
progress: rx,
pool,
}, tx)
}
/// Returns the progress of the job
@ -44,11 +36,11 @@ impl<E: Send + Sync + 'static> MultithreadedJob<E> {
}
/// Executes a function on the thread pool
pub fn execute<F>(&self, f: F)
pub fn execute<F>(&self, tx: &Sender<Result<(), E>>, f: F)
where
F: (FnOnce() -> Result<(), E>) + Send + 'static,
{
let sender = self.sender.clone();
let sender = tx.clone();
self.pool.execute(move || {
let result = f();

View file

@ -34,7 +34,7 @@ impl InMemoryIndex {
.entry(scope.to_string())
.or_insert_with(|| (BTreeSet::new(), IndexFile::default()))
.1
.push(index_file);
.insert(index_file);
self
}
}
@ -65,7 +65,7 @@ impl Index for InMemoryIndex {
&mut self,
manifest: &Manifest,
uploader: &u64,
) -> Result<bool, CreatePackageVersionError> {
) -> Result<Option<IndexFileEntry>, CreatePackageVersionError> {
let scope = manifest.name.scope();
if let Some(owners) = self.scope_owners(scope)? {
@ -78,9 +78,10 @@ impl Index for InMemoryIndex {
let package = self.packages.get_mut(scope).unwrap();
package.1.push(manifest.clone().into());
let entry: IndexFileEntry = manifest.clone().into();
package.1.insert(entry.clone());
Ok(true)
Ok(Some(entry))
}
fn config(&self) -> Result<IndexConfig, ConfigError> {