mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: 🚸 add wally conversion
This commit is contained in:
parent
4b0aca4eb0
commit
9e1ffc41b6
10 changed files with 254 additions and 59 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -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(¶ms.cwd)?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
|
@ -152,6 +152,8 @@ impl GitPackageRef {
|
|||
}
|
||||
|
||||
repo.reset(&obj, git2::ResetType::Hard, None)?;
|
||||
|
||||
Manifest::from_path_or_convert(dest)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
29
src/index.rs
29
src/index.rs
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
|
|
179
src/manifest.rs
179
src/manifest.rs
|
@ -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
|
||||
|
|
|
@ -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,12 +36,12 @@ 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();
|
||||
sender.send(result).unwrap();
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in a new issue