Compare commits

...

3 Commits

Author SHA1 Message Date
Aumetra Weisman 2f364c2313
Merge 6008ccf0af into 8586d06b1f 2024-05-08 23:26:45 +02:00
Aumetra Weisman 6008ccf0af
Merge branch 'main' into svelte5-frontend 2024-05-08 23:26:42 +02:00
Aumetra Weisman 8586d06b1f
Update Meilisearch SDK (#527)
* upgrade meilisearch sdk

* restructure
2024-05-08 19:48:53 +02:00
35 changed files with 423 additions and 509 deletions

View File

@ -4,7 +4,6 @@
"editor.formatOnSave": true,
"rust-analyzer.showUnlinkedFileNotification": false,
"rust-analyzer.cargo.features": [
"meilisearch",
"oidc"
],
"rust-analyzer.check.extraArgs": [

635
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ kitsune-util = { path = "../kitsune-util" }
kitsune-wasm-mrf = { path = "../kitsune-wasm-mrf" }
mime = "0.3.17"
mime_guess = { version = "2.0.4", default-features = false }
serde = "1.0.200"
serde = "1.0.201"
sha2 = "0.10.8"
simd-json = "0.13.10"
speedy-uuid = { path = "../../lib/speedy-uuid" }

View File

@ -14,7 +14,7 @@ redis = { version = "0.25.3", default-features = false, features = [
"connection-manager",
"tokio-comp",
] }
serde = "1.0.200"
serde = "1.0.201"
simd-json = "0.13.10"
tracing = "0.1.40"
typed-builder = "0.18.2"

View File

@ -10,7 +10,7 @@ enum_dispatch = "0.3.13"
http = "1.1.0"
kitsune-error = { path = "../kitsune-error" }
kitsune-http-client = { path = "../kitsune-http-client" }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
serde_urlencoded = "0.7.1"
simd-json = "0.13.10"
strum = { version = "0.26.2", features = ["derive"] }

View File

@ -9,7 +9,7 @@ license.workspace = true
eyre = "0.6.12"
human-size = { version = "0.4.3", features = ["serde"] }
isolang = { version = "2.4.0", features = ["serde"] }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
smol_str = { version = "0.2.1", features = ["serde"] }
tokio = { version = "1.37.0", features = ["fs"] }
toml = { version = "0.8.12", default-features = false, features = ["parse"] }

View File

@ -11,7 +11,7 @@ async-trait = "0.1.80"
const_format = "0.2.32"
kitsune-db = { path = "../kitsune-db" }
kitsune-error = { path = "../kitsune-error" }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
typed-builder = "0.18.2"
[build-dependencies]

View File

@ -34,7 +34,7 @@ rustls = { version = "0.23.5", default-features = false, features = [
"tls12",
] }
rustls-native-certs = "0.7.0"
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
simd-json = "0.13.10"
speedy-uuid = { path = "../../lib/speedy-uuid", features = ["diesel"] }
tokio = { version = "1.37.0", features = ["rt"] }

View File

@ -31,7 +31,7 @@ hyper-rustls = { version = "0.27.1", default-features = false, features = [
] }
kitsune-type = { path = "../kitsune-type" }
pin-project = "1.1.5"
serde = "1.0.200"
serde = "1.0.201"
simdutf8 = { version = "0.1.4", features = ["aarch64_neon"] }
simd-json = "0.13.10"
tower = { version = "0.4.13", features = ["util"] }

View File

@ -15,7 +15,7 @@ kitsune-core = { path = "../kitsune-core" }
kitsune-db = { path = "../kitsune-db" }
kitsune-email = { path = "../kitsune-email" }
kitsune-error = { path = "../kitsune-error" }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
speedy-uuid = { path = "../../lib/speedy-uuid" }
tracing = "0.1.40"
typed-builder = "0.18.2"

View File

@ -19,7 +19,7 @@ kitsune-type = { path = "../kitsune-type" }
kitsune-url = { path = "../kitsune-url" }
kitsune-util = { path = "../kitsune-util" }
mime = "0.3.17"
serde = "1.0.200"
serde = "1.0.201"
simd-json = "0.13.10"
smol_str = "0.2.1"
speedy-uuid = { path = "../../lib/speedy-uuid" }

View File

@ -25,7 +25,7 @@ redis = { version = "0.25.3", default-features = false, features = [
"connection-manager",
"tokio-comp",
] }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
simd-json = "0.13.10"
speedy-uuid = { path = "../../lib/speedy-uuid", features = ["serde"] }
url = "2.5.0"

View File

@ -13,7 +13,7 @@ kitsune-error = { path = "../kitsune-error" }
kitsune-http-client = { path = "../kitsune-http-client" }
quick-xml = { version = "0.31.0", features = ["serialize"] }
rusty-s3 = "0.5.0"
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
typed-builder = "0.18.2"
[dev-dependencies]

View File

@ -6,7 +6,7 @@ version.workspace = true
license.workspace = true
[dependencies]
anyhow = "1.0.82"
anyhow = "1.0.83"
glob = "0.3.1"
rsass = "0.28.8"
tracing = { version = "0.1.40", default-features = false }

View File

@ -5,34 +5,29 @@ edition.workspace = true
version.workspace = true
license.workspace = true
[package.metadata.cargo-machete]
ignored = ["isahc"] # To make `meilisearch` builds static
[dependencies]
async-trait = "0.1.80"
bytes = "1.6.0"
diesel = "2.1.6"
diesel-async = "0.4.1"
diesel_full_text_search = { version = "2.1.1", default-features = false }
enum_dispatch = "0.3.13"
futures-io = "0.3.30"
futures-util = "0.3.30"
http = "1.1.0"
kitsune-config = { path = "../kitsune-config" }
kitsune-db = { path = "../kitsune-db" }
kitsune-error = { path = "../kitsune-error" }
kitsune-http-client = { path = "../kitsune-http-client" }
kitsune-language = { path = "../kitsune-language" }
serde = { version = "1.0.200", features = ["derive"] }
meilisearch-sdk = { version = "0.26.0", default-features = false }
pin-project-lite = "0.2.14"
serde = { version = "1.0.201", features = ["derive"] }
serde_urlencoded = "0.7.1"
speedy-uuid = { path = "../../lib/speedy-uuid" }
strum = { version = "0.26.2", features = ["derive"] }
tracing = "0.1.40"
typed-builder = "0.18.2"
# "meilisearch" feature
isahc = { version = "1.7.2", default-features = false, features = [
"static-ssl",
], optional = true }
meilisearch-sdk = { version = "0.25.0", optional = true }
[features]
default = []
meilisearch = ["dep:isahc", "dep:meilisearch-sdk"]
[lints]
workspace = true

View File

@ -8,18 +8,15 @@ use serde::{Deserialize, Serialize};
use speedy_uuid::Uuid;
use strum::{AsRefStr, EnumIter};
#[cfg(feature = "meilisearch")]
mod meilisearch;
mod sql;
#[cfg(feature = "meilisearch")]
pub use self::meilisearch::MeiliSearchService;
pub use self::sql::SearchService as SqlSearchService;
#[derive(Clone)]
#[enum_dispatch(SearchBackend)]
pub enum AnySearchBackend {
#[cfg(feature = "meilisearch")]
Meilisearch(MeiliSearchService),
Noop(NoopSearchService),
Sql(SqlSearchService),

View File

@ -0,0 +1,121 @@
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::Stream;
use http::header::CONTENT_TYPE;
use kitsune_http_client::Body as HttpBody;
use meilisearch_sdk::{errors::Error as MeilisearchError, request::Method};
use pin_project_lite::pin_project;
use serde::{de::DeserializeOwned, Serialize};
use std::{
io,
pin::Pin,
task::{self, ready, Poll},
};
const BUFFER_SIZE: usize = 1024;
pin_project! {
struct AsyncReadBridge<R> {
#[pin]
inner: R,
buf: Vec<u8>,
}
}
impl<R> AsyncReadBridge<R> {
pub fn new(reader: R, buf_size: usize) -> Self {
Self {
inner: reader,
buf: vec![0; buf_size],
}
}
}
impl<R> Stream for AsyncReadBridge<R>
where
R: futures_io::AsyncRead,
{
type Item = io::Result<Bytes>;
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
let amount_read = match ready!(this.inner.poll_read(cx, this.buf)) {
Ok(0) => return Poll::Ready(None),
Ok(amount_read) => amount_read,
Err(err) => return Poll::Ready(Some(Err(err))),
};
let bytes = Bytes::copy_from_slice(&this.buf[..amount_read]);
this.buf.clear();
this.buf.fill(0);
Poll::Ready(Some(Ok(bytes)))
}
}
#[derive(Clone)]
pub struct HttpClient {
pub inner: kitsune_http_client::Client,
}
#[async_trait]
impl meilisearch_sdk::request::HttpClient for HttpClient {
async fn stream_request<
Query: Serialize + Send + Sync,
Body: futures_io::AsyncRead + Send + Sync + 'static,
Output: DeserializeOwned + 'static,
>(
&self,
url: &str,
method: Method<Query, Body>,
content_type: &str,
expected_status_code: u16,
) -> Result<Output, MeilisearchError> {
let url = format!(
"{url}?{}",
serde_urlencoded::to_string(method.query())
.map_err(|err| MeilisearchError::Other(err.into()))?
);
let request = http::Request::builder()
.uri(&url)
.header(CONTENT_TYPE, content_type);
let request = match method {
Method::Get { .. } => request.method(http::Method::GET),
Method::Post { .. } => request.method(http::Method::POST),
Method::Patch { .. } => request.method(http::Method::PATCH),
Method::Put { .. } => request.method(http::Method::PUT),
Method::Delete { .. } => request.method(http::Method::DELETE),
};
let body = method
.map_body(|body| HttpBody::stream(AsyncReadBridge::new(body, BUFFER_SIZE)))
.into_body()
.unwrap_or_default();
let request = request
.body(body)
.map_err(|err| MeilisearchError::Other(err.into()))?;
let response = self
.inner
.execute(request)
.await
.map_err(|err| MeilisearchError::Other(err.into()))?;
if response.status().as_u16() != expected_status_code {
return Err(meilisearch_sdk::errors::MeilisearchCommunicationError {
status_code: response.status().as_u16(),
message: response.text().await.ok(),
url,
}
.into());
}
response
.json()
.await
.map_err(|err| MeilisearchError::Other(err.into()))
}
}

View File

@ -1,9 +1,12 @@
use self::http_client::HttpClient;
use super::{Result, SearchBackend, SearchIndex, SearchItem, SearchResultReference};
use meilisearch_sdk::{indexes::Index, settings::Settings, Client};
use meilisearch_sdk::{client::Client, indexes::Index, settings::Settings};
use serde::Deserialize;
use speedy_uuid::Uuid;
use strum::IntoEnumIterator;
mod http_client;
#[derive(Deserialize)]
struct MeilisearchResult {
id: Uuid,
@ -11,7 +14,7 @@ struct MeilisearchResult {
#[derive(Clone)]
pub struct MeiliSearchService {
client: Client,
client: Client<HttpClient>,
}
impl MeiliSearchService {
@ -21,9 +24,15 @@ impl MeiliSearchService {
///
/// - Failed to connect to the instance
pub async fn new(host: &str, api_key: &str) -> Result<Self> {
let service = Self {
client: Client::new(host, Some(api_key)),
let http_client = HttpClient {
inner: kitsune_http_client::Client::builder()
.content_length_limit(None)
.build(),
};
let service = Self {
client: Client::new_with_client(host, Some(api_key), http_client),
};
let settings = Settings::new()
.with_filterable_attributes(["created_at"])
.with_sortable_attributes(["id"]);
@ -40,7 +49,7 @@ impl MeiliSearchService {
Ok(service)
}
fn get_index(&self, index: SearchIndex) -> Index {
fn get_index(&self, index: SearchIndex) -> Index<HttpClient> {
self.client.index(index.as_ref())
}
}

View File

@ -54,7 +54,7 @@ redis = { version = "0.25.3", default-features = false, features = [
] }
rsa = "0.9.6"
rusty-s3 = { version = "0.5.0", default-features = false }
serde = "1.0.200"
serde = "1.0.201"
smol_str = "0.2.1"
speedy-uuid = { path = "../../lib/speedy-uuid" }
tokio = { version = "1.37.0", features = ["macros", "sync"] }
@ -63,9 +63,6 @@ typed-builder = "0.18.2"
url = "2.5.0"
zxcvbn = { version = "2.2.2", default-features = false }
[features]
meilisearch = ["kitsune-search/meilisearch"]
[dev-dependencies]
hex-simd = "0.8.0"
http-body-util = "0.1.1"

View File

@ -1,3 +1,4 @@
use eyre::WrapErr;
use kitsune_cache::{ArcCache, InMemoryCache, NoopCache, RedisCache};
use kitsune_captcha::AnyCaptcha;
use kitsune_captcha::{hcaptcha::Captcha as HCaptcha, mcaptcha::Captcha as MCaptcha};
@ -130,21 +131,12 @@ pub async fn search(
db_pool: &PgPool,
) -> eyre::Result<AnySearchBackend> {
let service = match search_config {
search::Configuration::Meilisearch(_config) => {
#[cfg(not(feature = "meilisearch"))]
panic!("Server compiled without Meilisearch compatibility");
#[cfg(feature = "meilisearch")]
#[allow(clippy::used_underscore_binding)]
{
use eyre::WrapErr;
kitsune_search::MeiliSearchService::new(&_config.instance_url, &_config.api_key)
.await
.map_err(kitsune_error::Error::into_error)
.wrap_err("Failed to connect to Meilisearch")?
.into()
}
search::Configuration::Meilisearch(config) => {
kitsune_search::MeiliSearchService::new(&config.instance_url, &config.api_key)
.await
.map_err(kitsune_error::Error::into_error)
.wrap_err("Failed to connect to Meilisearch")?
.into()
}
search::Configuration::Sql => SqlSearchService::builder()
.db_pool(db_pool.clone())

View File

@ -13,7 +13,7 @@ kitsune-error = { path = "../kitsune-error" }
kitsune-s3 = { path = "../kitsune-s3" }
rusty-s3 = { version = "0.5.0", default-features = false }
tokio = { version = "1.37.0", features = ["fs", "io-util"] }
tokio-util = { version = "0.7.10", features = ["io"] }
tokio-util = { version = "0.7.11", features = ["io"] }
[dev-dependencies]
tempfile = "3.10.1"

View File

@ -7,12 +7,12 @@ license.workspace = true
[dependencies]
iso8601-timestamp = "0.2.17"
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
simd-json = "0.13.10"
smol_str = { version = "0.2.1", features = ["serde"] }
speedy-uuid = { path = "../../lib/speedy-uuid", features = ["serde"] }
strum = { version = "0.26.2", features = ["derive"] }
utoipa = { version = "4.2.0", features = ["chrono", "uuid"] }
utoipa = { version = "4.2.3", features = ["chrono", "uuid"] }
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@ -32,7 +32,7 @@ tokio = { version = "1.37.0", features = ["fs"] }
tracing = "0.1.40"
typed-builder = "0.18.2"
walkdir = "2.5.0"
wasmtime = { version = "20.0.1", default-features = false, features = [
wasmtime = { version = "20.0.2", default-features = false, features = [
"addr2line",
"async",
"component-model",
@ -41,13 +41,13 @@ wasmtime = { version = "20.0.1", default-features = false, features = [
"pooling-allocator",
"runtime",
] }
wasmtime-wasi = { version = "20.0.1", default-features = false }
wasmtime-wasi = { version = "20.0.2", default-features = false }
[dev-dependencies]
tempfile = "3.10.1"
tokio = { version = "1.37.0", features = ["macros", "rt"] }
tracing-subscriber = "0.3.18"
wat = "1.206.0"
wat = "1.207.0"
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
# Search
Kitsune has a number of search backends, each different from the other, to best fit your specific needs.
Kitsune has a number of search backends, each different from the other, to best fit your specific needs.
We want to give you a brief overview over the available backends.
## No Search
@ -23,8 +23,6 @@ This runs searches on your database directly. The quality is actually not too ba
## Meilisearch
> You need to compile Kitsune with the `meilisearch` feature flag to enable this feature
```toml
[search]
type = "meilisearch"

View File

@ -22,7 +22,7 @@ envy = "0.4.2"
kitsune-config = { path = "../crates/kitsune-config" }
kitsune-db = { path = "../crates/kitsune-db" }
kitsune-error = { path = "../crates/kitsune-error" }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
speedy-uuid = { path = "../lib/speedy-uuid" }
tokio = { version = "1.37.0", features = ["full"] }
tracing-subscriber = "0.3.18"

View File

@ -80,7 +80,7 @@ redis = { version = "0.25.3", default-features = false, features = [
] }
rust-embed = { version = "8.3.0", features = ["include-exclude"] }
scoped-futures = "0.1.3"
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
serde_urlencoded = "0.7.1"
simd-json = "0.13.10"
simdutf8 = { version = "0.1.4", features = ["aarch64_neon"] }
@ -89,7 +89,7 @@ strum = { version = "0.26.2", features = ["derive", "phf"] }
tempfile = "3.10.1"
time = "0.3.36"
tokio = { version = "1.37.0", features = ["full"] }
tokio-util = { version = "0.7.10", features = ["compat"] }
tokio-util = { version = "0.7.11", features = ["compat"] }
tower = { version = "0.4.13", features = ["util"] }
tower-stop-using-brave = { path = "../lib/tower-stop-using-brave" }
tower-x-clacks-overhead = { path = "../lib/tower-x-clacks-overhead" }
@ -106,8 +106,8 @@ tracing = "0.1.40"
trials = { path = "../lib/trials" }
typed-builder = "0.18.2"
url = "2.5.0"
utoipa = { version = "4.2.0", features = ["axum_extras", "uuid"] }
utoipa-swagger-ui = { version = "6.0.0", features = ["axum"] }
utoipa = { version = "4.2.3", features = ["axum_extras", "uuid"] }
utoipa-swagger-ui = { version = "=6.0.0", features = ["axum"] }
# --- Optional dependencies ---
@ -151,7 +151,6 @@ graphql-api = [
"speedy-uuid/async-graphql",
]
mastodon-api = ["dep:kitsune-mastodon"]
meilisearch = ["kitsune-search/meilisearch", "kitsune-service/meilisearch"]
oidc = ["dep:kitsune-oidc"]
[lints]

View File

@ -26,13 +26,13 @@ redis = { version = "0.25.3", default-features = false, features = [
"streams",
"tokio-comp",
], optional = true }
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
simd-json = { version = "0.13.10", optional = true }
smol_str = "0.2.1"
speedy-uuid = { path = "../speedy-uuid", features = ["redis", "serde"] }
thiserror = "1.0.59"
thiserror = "1.0.60"
tokio = { version = "1.37.0", features = ["macros", "rt", "sync"] }
tokio-util = { version = "0.7.10", features = ["rt"] }
tokio-util = { version = "0.7.11", features = ["rt"] }
tracing = "0.1.40"
typed-builder = "0.18.2"
typetag = "0.2.16"

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
once_cell = "1.19.0"
rayon = "1.10.0"
thiserror = "1.0.59"
thiserror = "1.0.60"
tokio = { version = "1.37.0", features = ["sync"] }
tracing = "0.1.40"

View File

@ -9,16 +9,16 @@ license = "MIT OR Apache-2.0"
async-trait = "0.1.80"
hickory-resolver = { version = "0.24.1", features = ["dns-over-rustls"] }
rand = "0.8.5"
serde = { version = "1.0.200", features = ["derive"] }
serde = { version = "1.0.201", features = ["derive"] }
simdutf8 = { version = "0.1.4", features = ["aarch64_neon"] }
thiserror = "1.0.59"
thiserror = "1.0.60"
tracing = "0.1.40"
typed-builder = "0.18.2"
[dev-dependencies]
insta = { version = "1.38.0", features = ["json"] }
rand_xorshift = "0.3.0"
serde_json = "1.0.116"
serde_json = "1.0.117"
tokio = { version = "1.37.0", features = ["macros", "rt"] }
[lints]

View File

@ -31,7 +31,7 @@ miette = "7.2.0"
pkcs8 = { version = "0.10.2", features = ["pem", "std"] }
ring = { version = "0.17.8", features = ["std"] }
scoped-futures = { version = "0.1.3", default-features = false }
thiserror = "1.0.59"
thiserror = "1.0.60"
tick-tock-mock = { path = "../tick-tock-mock" }
tracing = { version = "0.1.40", default-features = false, optional = true }

View File

@ -8,18 +8,18 @@ license = "MIT OR Apache-2.0"
[dependencies]
leb128 = { version = "0.2.5", optional = true }
olpc-cjson = { version = "0.1.3", optional = true }
schemars = { version = "0.8.17", features = ["impl_json_schema", "semver"] }
semver = { version = "1.0.22", features = ["serde"] }
serde = { version = "1.0.200", features = ["derive"] }
serde_json = { version = "1.0.116", optional = true }
thiserror = { version = "1.0.59", optional = true }
wasm-encoder = { version = "0.206.0", optional = true }
wasmparser = { version = "0.206.0", optional = true }
schemars = { version = "0.8.19", features = ["impl_json_schema", "semver"] }
semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0.201", features = ["derive"] }
serde_json = { version = "1.0.117", optional = true }
thiserror = { version = "1.0.60", optional = true }
wasm-encoder = { version = "0.207.0", optional = true }
wasmparser = { version = "0.207.0", optional = true }
[dev-dependencies]
serde_json = "1.0.116"
serde_json = "1.0.117"
insta = { version = "1.38.0", default-features = false, features = ["json"] }
wat = "1.206.0"
wat = "1.207.0"
[features]
decode = ["dep:leb128", "dep:serde_json", "dep:thiserror", "dep:wasmparser"]

View File

@ -14,12 +14,12 @@ mrf-manifest = { path = "../mrf-manifest", features = [
"encode",
"serialise",
] }
serde_json = "1.0.116"
wasmparser = "0.206.0"
serde_json = "1.0.117"
wasmparser = "0.207.0"
[lints]
workspace = true
[dev-dependencies]
serde_json = "1.0.116"
wat = "1.206.0"
serde_json = "1.0.117"
wat = "1.207.0"

View File

@ -12,8 +12,8 @@ diesel = { version = "2.1.6", features = [
"uuid",
], optional = true }
redis = { version = "0.25.3", default-features = false, optional = true }
serde = { version = "1.0.200", optional = true }
thiserror = "1.0.59"
serde = { version = "1.0.201", optional = true }
thiserror = "1.0.60"
uuid = { version = "1.8.0", features = ["fast-rng", "v7"] }
uuid-simd = { version = "0.8.0", features = ["uuid"] }

View File

@ -9,9 +9,9 @@ license = "MIT OR Apache-2.0"
proc-macro = true
[dependencies]
proc-macro2 = "1.0.81"
proc-macro2 = "1.0.82"
quote = "1.0.36"
syn = { version = "2.0.60", features = ["full", "visit-mut"] }
syn = { version = "2.0.61", features = ["full", "visit-mut"] }
[lints]
workspace = true

View File

@ -6,7 +6,7 @@ license.workspace = true
publish = false
[dependencies]
anyhow = "1.0.82"
anyhow = "1.0.83"
argh = "0.1.12"
glob = "0.3.1"
taplo = { version = "0.13.0", default-features = false }