mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: store more package info in index
This commit is contained in:
parent
cc85135a8e
commit
7aaea85a2d
11 changed files with 158 additions and 105 deletions
|
@ -37,15 +37,15 @@ pub async fn authentication(
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = if token.to_lowercase().starts_with("bearer ") {
|
let token = if token.to_lowercase().starts_with("bearer ") {
|
||||||
token[7..].to_string()
|
|
||||||
} else {
|
|
||||||
token.to_string()
|
token.to_string()
|
||||||
|
} else {
|
||||||
|
format!("Bearer {token}")
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = match app_state
|
let response = match app_state
|
||||||
.reqwest_client
|
.reqwest_client
|
||||||
.get("https://api.github.com/user")
|
.get("https://api.github.com/user")
|
||||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {token}"))
|
.header(reqwest::header::AUTHORIZATION, token)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.and_then(|res| res.error_for_status())
|
.and_then(|res| res.error_for_status())
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
use actix_web::{http::header::ACCEPT, web, HttpRequest, HttpResponse, Responder};
|
|
||||||
use rusty_s3::{actions::GetObject, S3Action};
|
|
||||||
use semver::Version;
|
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
package::{s3_name, PackageResponse, S3_SIGN_DURATION},
|
package::{s3_name, PackageResponse, S3_SIGN_DURATION},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
use actix_web::{
|
||||||
|
http::header::{ACCEPT, LOCATION},
|
||||||
|
web, HttpRequest, HttpResponse, Responder,
|
||||||
|
};
|
||||||
use pesde::{
|
use pesde::{
|
||||||
manifest::target::TargetKind,
|
manifest::target::TargetKind,
|
||||||
names::PackageName,
|
names::PackageName,
|
||||||
source::{git_index::GitBasedSource, pesde::IndexFile},
|
source::{git_index::GitBasedSource, pesde::IndexFile},
|
||||||
};
|
};
|
||||||
|
use rusty_s3::{actions::GetObject, S3Action};
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VersionRequest {
|
pub enum VersionRequest {
|
||||||
|
@ -68,29 +70,27 @@ pub async fn get_package_version(
|
||||||
return Ok(HttpResponse::NotFound().finish());
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
if request
|
let accept = request
|
||||||
.headers()
|
.headers()
|
||||||
.get(ACCEPT)
|
.get(ACCEPT)
|
||||||
.and_then(|accept| accept.to_str().ok())
|
.and_then(|accept| accept.to_str().ok())
|
||||||
.is_some_and(|accept| accept.eq_ignore_ascii_case("application/octet-stream"))
|
.and_then(|accept| match accept.to_lowercase().as_str() {
|
||||||
{
|
"text/plain" => Some(true),
|
||||||
|
"application/octet-stream" => Some(false),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(readme) = accept {
|
||||||
let object_url = GetObject::new(
|
let object_url = GetObject::new(
|
||||||
&app_state.s3_bucket,
|
&app_state.s3_bucket,
|
||||||
Some(&app_state.s3_credentials),
|
Some(&app_state.s3_credentials),
|
||||||
&s3_name(&name, &v_id),
|
&s3_name(&name, &v_id, readme),
|
||||||
)
|
)
|
||||||
.sign(S3_SIGN_DURATION);
|
.sign(S3_SIGN_DURATION);
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok().body(
|
return Ok(HttpResponse::TemporaryRedirect()
|
||||||
app_state
|
.append_header((LOCATION, object_url.as_str()))
|
||||||
.reqwest_client
|
.finish());
|
||||||
.get(object_url)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.bytes()
|
|
||||||
.await?,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(PackageResponse {
|
Ok(HttpResponse::Ok().json(PackageResponse {
|
||||||
|
@ -100,5 +100,7 @@ pub async fn get_package_version(
|
||||||
description: entry.description.clone().unwrap_or_default(),
|
description: entry.description.clone().unwrap_or_default(),
|
||||||
published_at: entry.published_at,
|
published_at: entry.published_at,
|
||||||
license: entry.license.clone().unwrap_or_default(),
|
license: entry.license.clone().unwrap_or_default(),
|
||||||
|
authors: entry.authors.clone(),
|
||||||
|
repository: entry.repository.clone().map(|url| url.to_string()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ pub async fn get_package_versions(
|
||||||
description: entry.description.unwrap_or_default(),
|
description: entry.description.unwrap_or_default(),
|
||||||
published_at: entry.published_at,
|
published_at: entry.published_at,
|
||||||
license: entry.license.unwrap_or_default(),
|
license: entry.license.unwrap_or_default(),
|
||||||
|
authors: entry.authors.clone(),
|
||||||
|
repository: entry.repository.clone().map(|url| url.to_string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
info.targets.insert(entry.target.into());
|
info.targets.insert(entry.target.into());
|
||||||
|
|
|
@ -8,19 +8,12 @@ use actix_web::{web, HttpResponse, Responder};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use git2::{Remote, Repository, Signature};
|
use git2::{Remote, Repository, Signature};
|
||||||
|
use reqwest::header::{CONTENT_ENCODING, CONTENT_TYPE};
|
||||||
use rusty_s3::{actions::PutObject, S3Action};
|
use rusty_s3::{actions::PutObject, S3Action};
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
auth::UserId,
|
|
||||||
benv,
|
|
||||||
error::{Error, ErrorResponse},
|
|
||||||
package::{s3_name, S3_SIGN_DURATION},
|
|
||||||
search::update_version,
|
|
||||||
AppState,
|
|
||||||
};
|
|
||||||
use pesde::{
|
use pesde::{
|
||||||
manifest::{DependencyType, Manifest},
|
manifest::Manifest,
|
||||||
source::{
|
source::{
|
||||||
git_index::GitBasedSource,
|
git_index::GitBasedSource,
|
||||||
pesde::{IndexFile, IndexFileEntry, ScopeInfo, SCOPE_INFO_FILE},
|
pesde::{IndexFile, IndexFileEntry, ScopeInfo, SCOPE_INFO_FILE},
|
||||||
|
@ -31,6 +24,15 @@ use pesde::{
|
||||||
MANIFEST_FILE_NAME,
|
MANIFEST_FILE_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
auth::UserId,
|
||||||
|
benv,
|
||||||
|
error::{Error, ErrorResponse},
|
||||||
|
package::{s3_name, S3_SIGN_DURATION},
|
||||||
|
search::update_version,
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
|
||||||
fn signature<'a>() -> Signature<'a> {
|
fn signature<'a>() -> Signature<'a> {
|
||||||
Signature::now(
|
Signature::now(
|
||||||
&benv!(required "COMMITTER_GIT_NAME"),
|
&benv!(required "COMMITTER_GIT_NAME"),
|
||||||
|
@ -80,6 +82,7 @@ pub async fn publish_package(
|
||||||
|
|
||||||
let entries = archive.entries()?;
|
let entries = archive.entries()?;
|
||||||
let mut manifest = None::<Manifest>;
|
let mut manifest = None::<Manifest>;
|
||||||
|
let mut readme = None::<Vec<u8>>;
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let mut entry = entry?;
|
let mut entry = entry?;
|
||||||
|
@ -107,6 +110,21 @@ pub async fn publish_package(
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
entry.read_to_string(&mut content)?;
|
entry.read_to_string(&mut content)?;
|
||||||
manifest = Some(toml::de::from_str(&content).map_err(|_| Error::InvalidArchive)?);
|
manifest = Some(toml::de::from_str(&content).map_err(|_| Error::InvalidArchive)?);
|
||||||
|
} else if path.to_lowercase() == "readme"
|
||||||
|
|| path
|
||||||
|
.to_lowercase()
|
||||||
|
.split_once('.')
|
||||||
|
.filter(|(file, ext)| *file == "readme" && (*ext == "md" || *ext == "txt"))
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
if readme.is_some() {
|
||||||
|
return Err(Error::InvalidArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut gz = flate2::read::GzEncoder::new(entry, flate2::Compression::best());
|
||||||
|
let mut bytes = vec![];
|
||||||
|
gz.read_to_end(&mut bytes)?;
|
||||||
|
readme = Some(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,59 +139,52 @@ pub async fn publish_package(
|
||||||
|
|
||||||
let dependencies = manifest
|
let dependencies = manifest
|
||||||
.all_dependencies()
|
.all_dependencies()
|
||||||
.map_err(|_| Error::InvalidArchive)?
|
.map_err(|_| Error::InvalidArchive)?;
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(alias, (specifier, ty))| {
|
|
||||||
match &specifier {
|
|
||||||
DependencySpecifiers::Pesde(specifier) => {
|
|
||||||
if specifier
|
|
||||||
.index
|
|
||||||
.as_ref()
|
|
||||||
.filter(|index| match index.parse::<url::Url>() {
|
|
||||||
Ok(_) if config.other_registries_allowed => true,
|
|
||||||
Ok(url) => url == env!("CARGO_PKG_REPOSITORY").parse().unwrap(),
|
|
||||||
Err(_) => false,
|
|
||||||
})
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
return Some(Err(Error::InvalidArchive));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (dep_scope, dep_name) = specifier.name.as_str();
|
for (specifier, _) in dependencies.values() {
|
||||||
match source.read_file([dep_scope, dep_name], &app_state.project, None) {
|
match specifier {
|
||||||
Ok(Some(_)) => {}
|
DependencySpecifiers::Pesde(specifier) => {
|
||||||
Ok(None) => return Some(Err(Error::InvalidArchive)),
|
if specifier
|
||||||
Err(e) => return Some(Err(e.into())),
|
.index
|
||||||
}
|
.as_ref()
|
||||||
|
.filter(|index| match index.parse::<url::Url>() {
|
||||||
|
Ok(_) if config.other_registries_allowed => true,
|
||||||
|
Ok(url) => url == env!("CARGO_PKG_REPOSITORY").parse().unwrap(),
|
||||||
|
Err(_) => false,
|
||||||
|
})
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidArchive);
|
||||||
}
|
}
|
||||||
DependencySpecifiers::Wally(specifier) => {
|
|
||||||
if !config.wally_allowed {
|
|
||||||
return Some(Err(Error::InvalidArchive));
|
|
||||||
}
|
|
||||||
|
|
||||||
if specifier
|
let (dep_scope, dep_name) = specifier.name.as_str();
|
||||||
.index
|
match source.read_file([dep_scope, dep_name], &app_state.project, None) {
|
||||||
.as_ref()
|
Ok(Some(_)) => {}
|
||||||
.filter(|index| index.parse::<url::Url>().is_ok())
|
Ok(None) => return Err(Error::InvalidArchive),
|
||||||
.is_none()
|
Err(e) => return Err(e.into()),
|
||||||
{
|
|
||||||
return Some(Err(Error::InvalidArchive));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DependencySpecifiers::Git(_) => {
|
|
||||||
if !config.git_allowed {
|
|
||||||
return Some(Err(Error::InvalidArchive));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if ty == DependencyType::Dev {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
DependencySpecifiers::Wally(specifier) => {
|
||||||
|
if !config.wally_allowed {
|
||||||
|
return Err(Error::InvalidArchive);
|
||||||
|
}
|
||||||
|
|
||||||
Some(Ok((alias, (specifier, ty))))
|
if specifier
|
||||||
})
|
.index
|
||||||
.collect::<Result<_, Error>>()?;
|
.as_ref()
|
||||||
|
.filter(|index| index.parse::<url::Url>().is_ok())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Err(Error::InvalidArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DependencySpecifiers::Git(_) => {
|
||||||
|
if !config.git_allowed {
|
||||||
|
return Err(Error::InvalidArchive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let repo = source.repo_git2(&app_state.project)?;
|
let repo = source.repo_git2(&app_state.project)?;
|
||||||
|
|
||||||
|
@ -209,6 +220,8 @@ pub async fn publish_package(
|
||||||
published_at: chrono::Utc::now(),
|
published_at: chrono::Utc::now(),
|
||||||
description: manifest.description.clone(),
|
description: manifest.description.clone(),
|
||||||
license: manifest.license.clone(),
|
license: manifest.license.clone(),
|
||||||
|
authors: manifest.authors.clone(),
|
||||||
|
repository: manifest.repository.clone(),
|
||||||
|
|
||||||
dependencies,
|
dependencies,
|
||||||
};
|
};
|
||||||
|
@ -219,10 +232,12 @@ pub async fn publish_package(
|
||||||
if let Some(this_version) = this_version {
|
if let Some(this_version) = this_version {
|
||||||
let other_entry = entries.get(this_version).unwrap();
|
let other_entry = entries.get(this_version).unwrap();
|
||||||
|
|
||||||
// TODO: should different licenses be allowed?
|
|
||||||
// description cannot be different - which one to render in the "Recently published" list?
|
// description cannot be different - which one to render in the "Recently published" list?
|
||||||
|
// the others cannot be different because what to return from the versions endpoint?
|
||||||
if other_entry.description != new_entry.description
|
if other_entry.description != new_entry.description
|
||||||
|| other_entry.license != new_entry.license
|
|| other_entry.license != new_entry.license
|
||||||
|
|| other_entry.authors != new_entry.authors
|
||||||
|
|| other_entry.repository != new_entry.repository
|
||||||
{
|
{
|
||||||
return Ok(HttpResponse::BadRequest().json(ErrorResponse {
|
return Ok(HttpResponse::BadRequest().json(ErrorResponse {
|
||||||
error: "same version with different description or license already exists"
|
error: "same version with different description or license already exists"
|
||||||
|
@ -297,23 +312,42 @@ pub async fn publish_package(
|
||||||
update_version(&app_state, &manifest.name, new_entry);
|
update_version(&app_state, &manifest.name, new_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let version_id = VersionId::new(manifest.version.clone(), manifest.target.kind());
|
||||||
|
|
||||||
let object_url = PutObject::new(
|
let object_url = PutObject::new(
|
||||||
&app_state.s3_bucket,
|
&app_state.s3_bucket,
|
||||||
Some(&app_state.s3_credentials),
|
Some(&app_state.s3_credentials),
|
||||||
&s3_name(
|
&s3_name(&manifest.name, &version_id, false),
|
||||||
&manifest.name,
|
|
||||||
&VersionId::new(manifest.version.clone(), manifest.target.kind()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.sign(S3_SIGN_DURATION);
|
.sign(S3_SIGN_DURATION);
|
||||||
|
|
||||||
app_state
|
app_state
|
||||||
.reqwest_client
|
.reqwest_client
|
||||||
.put(object_url)
|
.put(object_url)
|
||||||
|
.header(CONTENT_TYPE, "application/gzip")
|
||||||
|
.header(CONTENT_ENCODING, "gzip")
|
||||||
.body(bytes)
|
.body(bytes)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if let Some(readme) = readme {
|
||||||
|
let object_url = PutObject::new(
|
||||||
|
&app_state.s3_bucket,
|
||||||
|
Some(&app_state.s3_credentials),
|
||||||
|
&s3_name(&manifest.name, &version_id, true),
|
||||||
|
)
|
||||||
|
.sign(S3_SIGN_DURATION);
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.reqwest_client
|
||||||
|
.put(object_url)
|
||||||
|
.header(CONTENT_TYPE, "text/plain")
|
||||||
|
.header(CONTENT_ENCODING, "gzip")
|
||||||
|
.body(readme)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().body(format!(
|
Ok(HttpResponse::Ok().body(format!(
|
||||||
"published {}@{} {}",
|
"published {}@{} {}",
|
||||||
manifest.name, manifest.version, manifest.target
|
manifest.name, manifest.version, manifest.target
|
||||||
|
|
|
@ -103,6 +103,8 @@ pub async fn search_packages(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.published_at,
|
.published_at,
|
||||||
license: entry.license.clone().unwrap_or_default(),
|
license: entry.license.clone().unwrap_or_default(),
|
||||||
|
authors: entry.authors.clone(),
|
||||||
|
repository: entry.repository.clone().map(|url| url.to_string()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -7,10 +7,15 @@ use pesde::{
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{collections::BTreeSet, time::Duration};
|
use std::{collections::BTreeSet, time::Duration};
|
||||||
|
|
||||||
pub const S3_SIGN_DURATION: Duration = Duration::from_secs(60 * 60);
|
pub const S3_SIGN_DURATION: Duration = Duration::from_secs(60 * 3);
|
||||||
|
|
||||||
pub fn s3_name(name: &PackageName, version_id: &VersionId) -> String {
|
pub fn s3_name(name: &PackageName, version_id: &VersionId, is_readme: bool) -> String {
|
||||||
format!("{}+{}.tar.gz", name.escaped(), version_id.escaped())
|
format!(
|
||||||
|
"{}+{}{}",
|
||||||
|
name.escaped(),
|
||||||
|
version_id.escaped(),
|
||||||
|
if is_readme { "+readme.gz" } else { ".tar.gz" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||||
|
@ -22,11 +27,7 @@ pub struct TargetInfo {
|
||||||
|
|
||||||
impl From<Target> for TargetInfo {
|
impl From<Target> for TargetInfo {
|
||||||
fn from(target: Target) -> Self {
|
fn from(target: Target) -> Self {
|
||||||
TargetInfo {
|
(&target).into()
|
||||||
kind: target.kind(),
|
|
||||||
lib: target.lib_path().is_some(),
|
|
||||||
bin: target.bin_path().is_some(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,4 +63,8 @@ pub struct PackageResponse {
|
||||||
pub published_at: DateTime<Utc>,
|
pub published_at: DateTime<Utc>,
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
pub license: String,
|
pub license: String,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub authors: Vec<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub repository: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,14 +291,19 @@ impl PublishCommand {
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"authors: {}",
|
"authors: {}",
|
||||||
manifest
|
if manifest.authors.is_empty() {
|
||||||
.authors
|
"(none)".to_string()
|
||||||
.as_ref()
|
} else {
|
||||||
.map_or("(none)".to_string(), |a| a.join(", "))
|
manifest.authors.join(", ")
|
||||||
|
}
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"repository: {}",
|
"repository: {}",
|
||||||
manifest.repository.as_deref().unwrap_or("(none)")
|
manifest
|
||||||
|
.repository
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.as_str())
|
||||||
|
.unwrap_or("(none)")
|
||||||
);
|
);
|
||||||
|
|
||||||
let roblox_target = roblox_target.is_some_and(|_| true);
|
let roblox_target = roblox_target.is_some_and(|_| true);
|
||||||
|
|
|
@ -151,9 +151,7 @@ fn run() -> anyhow::Result<()> {
|
||||||
if let Some(token) = token {
|
if let Some(token) = token {
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::AUTHORIZATION,
|
reqwest::header::AUTHORIZATION,
|
||||||
format!("Bearer {token}")
|
token.parse().context("failed to create auth header")?,
|
||||||
.parse()
|
|
||||||
.context("failed to create auth header")?,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,11 @@ pub struct Manifest {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub license: Option<String>,
|
pub license: Option<String>,
|
||||||
/// The authors of the package
|
/// The authors of the package
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub authors: Option<Vec<String>>,
|
pub authors: Vec<String>,
|
||||||
/// The repository of the package
|
/// The repository of the package
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub repository: Option<String>,
|
pub repository: Option<url::Url>,
|
||||||
/// The target of the package
|
/// The target of the package
|
||||||
pub target: Target,
|
pub target: Target,
|
||||||
/// Whether the package is private
|
/// Whether the package is private
|
||||||
|
|
|
@ -280,8 +280,7 @@ impl Project {
|
||||||
pkg_ref.dependencies().clone()
|
pkg_ref.dependencies().clone()
|
||||||
{
|
{
|
||||||
if dependency_ty == DependencyType::Dev {
|
if dependency_ty == DependencyType::Dev {
|
||||||
// dev dependencies of dependencies are not included in the graph
|
// dev dependencies of dependencies are to be ignored
|
||||||
// they should not even be stored in the index, so this is just a check to avoid potential issues
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -402,6 +402,12 @@ pub struct IndexFileEntry {
|
||||||
/// The license of this package
|
/// The license of this package
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub license: Option<String>,
|
pub license: Option<String>,
|
||||||
|
/// The authors of this package
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub authors: Vec<String>,
|
||||||
|
/// The repository of this package
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub repository: Option<url::Url>,
|
||||||
|
|
||||||
/// The dependencies of this package
|
/// The dependencies of this package
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
|
Loading…
Reference in a new issue