Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 3074330

Browse files
authored
Improved package init and publish flow (#269)
When publishing either a package `Release` or `Init`, a new error `ConflictPendingPublish(RecordId)` may be returned. This instructs the client that it could wait for the conflicting pending record (on the same `LogId` package) to complete and then retry to publish. If a registry service offers key management and needs to sign the `Init` record, this enables a smoother experience and messaging on the CLI. Where the registry would return the `RecordId` of the registry signed `Init` record as a `ConflictPendingPublish` error, the CLI would wait for that record to publish and then retry. Since the package is already initialized, an error message would be presented that indicates that the package was initialized but not with the `Init` record that you signed. Also, added a feature flag for `cli-interactive` for the `warg-client` and a config flag to disable dialoguer interactive prompting even when that feature is enabled. Also, added paramenter to the `warg download` subcommand `--output` to write the downloaded `.wasm` file to a path in addition to the local cache.
1 parent 85f8ea1 commit 3074330

16 files changed

Lines changed: 376 additions & 1233 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 1122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ p256 = { workspace = true }
3131
rand_core = { workspace = true }
3232
url = { workspace = true }
3333
# TODO: remove these demo-related dependencies
34-
wasmtime = "10.0"
35-
wasmtime-wasi = "10.0"
3634
reqwest.workspace = true
3735
warg-api.workspace = true
3836
ptree.workspace = true
@@ -56,8 +54,9 @@ wit-parser = "0.13.1"
5654
testresult = "0.3.0"
5755

5856
[features]
59-
default = []
57+
default = ["cli-interactive"]
6058
postgres = ["warg-server/postgres"]
59+
cli-interactive = ["warg-client/cli-interactive"]
6160

6261
[workspace]
6362
members = ["crates/server"]
@@ -66,7 +65,7 @@ members = ["crates/server"]
6665
version = "0.5.0-dev"
6766
authors = ["The Warg Registry Project Developers"]
6867
edition = "2021"
69-
rust-version = "1.66.0"
68+
rust-version = "1.76.0"
7069
license = "Apache-2.0 WITH LLVM-exception"
7170
homepage = "https://warg.io/"
7271
repository = "https://github.com/bytecodealliance/registry"

crates/api/src/v1/content.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::Status;
55
use indexmap::IndexMap;
66
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
77
use std::borrow::Cow;
8+
use std::str::FromStr;
89
use thiserror::Error;
910
use warg_crypto::hash::AnyHash;
1011

@@ -95,7 +96,7 @@ impl<'de> Deserialize<'de> for ContentError {
9596
match RawError::<String>::deserialize(deserializer)? {
9697
RawError::NotFound { status: _, ty, id } => match ty {
9798
EntityType::ContentDigest => Ok(Self::ContentDigestNotFound(
98-
id.parse::<AnyHash>().map_err(|_| {
99+
AnyHash::from_str(&id).map_err(|_| {
99100
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid digest")
100101
})?,
101102
)),

crates/api/src/v1/fetch.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::Status;
44
use indexmap::IndexMap;
55
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
66
use std::borrow::Cow;
7+
use std::str::FromStr;
78
use thiserror::Error;
89
use warg_crypto::hash::AnyHash;
910
use warg_protocol::{
@@ -187,7 +188,7 @@ impl<'de> Deserialize<'de> for FetchError {
187188
RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
188189
RawError::NotFound { status: _, ty, id } => match ty {
189190
EntityType::Log => Ok(Self::LogNotFound(
190-
id.parse::<AnyHash>()
191+
AnyHash::from_str(&id)
191192
.map_err(|_| {
192193
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
193194
})?

crates/api/src/v1/package.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::Status;
55
use indexmap::IndexMap;
66
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
77
use std::borrow::Cow;
8+
use std::str::FromStr;
89
use thiserror::Error;
910
use warg_crypto::hash::AnyHash;
1011
use warg_protocol::{
@@ -133,6 +134,9 @@ pub enum PackageError {
133134
/// The operation was not supported by the registry.
134135
#[error("the requested operation is not supported: {0}")]
135136
NotSupported(String),
137+
/// The package was rejected by the registry, due to a conflict with a pending publish.
138+
#[error("the package conflicts with pending publish of record `{0}`")]
139+
ConflictPendingPublish(RecordId),
136140
/// The package was rejected by the registry.
137141
#[error("the package was rejected by the registry: {0}")]
138142
Rejection(String),
@@ -152,7 +156,7 @@ impl PackageError {
152156
match self {
153157
Self::Unauthorized { .. } => 401,
154158
Self::LogNotFound(_) | Self::RecordNotFound(_) | Self::NamespaceNotDefined(_) => 404,
155-
Self::NamespaceImported(_) => 409,
159+
Self::NamespaceImported(_) | Self::ConflictPendingPublish(_) => 409,
156160
Self::RecordNotSourcing => 405,
157161
Self::Rejection(_) => 422,
158162
Self::NotSupported(_) => 501,
@@ -243,6 +247,12 @@ impl Serialize for PackageError {
243247
id: Cow::Borrowed(namespace),
244248
}
245249
.serialize(serializer),
250+
Self::ConflictPendingPublish(record_id) => RawError::Conflict {
251+
status: Status::<409>,
252+
ty: EntityType::Record,
253+
id: Cow::Borrowed(record_id),
254+
}
255+
.serialize(serializer),
246256
Self::RecordNotSourcing => RawError::RecordNotSourcing::<()> {
247257
status: Status::<405>,
248258
}
@@ -277,14 +287,14 @@ impl<'de> Deserialize<'de> for PackageError {
277287
}
278288
RawError::NotFound { status: _, ty, id } => match ty {
279289
EntityType::Log => Ok(Self::LogNotFound(
280-
id.parse::<AnyHash>()
290+
AnyHash::from_str(&id)
281291
.map_err(|_| {
282292
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
283293
})?
284294
.into(),
285295
)),
286296
EntityType::Record => Ok(Self::RecordNotFound(
287-
id.parse::<AnyHash>()
297+
AnyHash::from_str(&id)
288298
.map_err(|_| {
289299
serde::de::Error::invalid_value(
290300
Unexpected::Str(&id),
@@ -301,6 +311,16 @@ impl<'de> Deserialize<'de> for PackageError {
301311
},
302312
RawError::Conflict { status: _, ty, id } => match ty {
303313
EntityType::NamespaceImport => Ok(Self::NamespaceImported(id.into_owned())),
314+
EntityType::Record => Ok(Self::ConflictPendingPublish(
315+
AnyHash::from_str(&id)
316+
.map_err(|_| {
317+
serde::de::Error::invalid_value(
318+
Unexpected::Str(&id),
319+
&"a valid record id",
320+
)
321+
})?
322+
.into(),
323+
)),
304324
_ => Err(serde::de::Error::invalid_value(
305325
Unexpected::Enum,
306326
&"a valid entity type",

crates/client/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ homepage = { workspace = true }
1010
repository = { workspace = true}
1111

1212
[features]
13-
default = []
13+
default = ["cli-interactive"]
1414
native-tls-vendored = ["reqwest/native-tls-vendored"]
15+
cli-interactive = ["dep:dialoguer"]
1516

1617
[dependencies]
1718
warg-crypto = { workspace = true }
@@ -24,7 +25,7 @@ clap = { workspace = true }
2425
serde = { workspace = true }
2526
serde_json = { workspace = true }
2627
tokio = { workspace = true }
27-
dialoguer = { workspace = true }
28+
dialoguer = { workspace = true, optional = true }
2829
tokio-util = { workspace = true }
2930
tempfile = { workspace = true }
3031
reqwest = { workspace = true }

crates/client/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ pub struct Config {
122122
/// Auto accept registry hint or ask the user to confirm
123123
#[serde(default)]
124124
pub auto_accept_federation_hints: bool,
125+
126+
/// Disable interactive prompts.
127+
#[serde(default)]
128+
pub disable_interactive: bool,
125129
}
126130

127131
impl Config {
@@ -202,6 +206,7 @@ impl Config {
202206
keyring_auth: self.keyring_auth,
203207
ignore_federation_hints: self.ignore_federation_hints,
204208
auto_accept_federation_hints: self.auto_accept_federation_hints,
209+
disable_interactive: self.disable_interactive,
205210
};
206211

207212
serde_json::to_writer_pretty(

crates/client/src/depsolve.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,16 @@ impl LockListBuilder {
4545
}
4646

4747
#[async_recursion]
48-
async fn parse_package<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage>(
48+
async fn parse_package<R, C, N>(
4949
&mut self,
5050
client: &Client<R, C, N>,
5151
mut bytes: &[u8],
52-
) -> Result<()> {
52+
) -> Result<()>
53+
where
54+
R: RegistryStorage,
55+
C: ContentStorage,
56+
N: NamespaceMapStorage,
57+
{
5358
let mut parser = Parser::new(0);
5459
let mut imports: Vec<String> = Vec::new();
5560
loop {
@@ -157,11 +162,16 @@ impl LockListBuilder {
157162

158163
/// List of deps for building
159164
#[async_recursion]
160-
pub async fn build_list<R: RegistryStorage, C: ContentStorage, N: NamespaceMapStorage>(
165+
pub async fn build_list<R, C, N>(
161166
&mut self,
162167
client: &Client<R, C, N>,
163168
info: &PackageInfo,
164-
) -> Result<()> {
169+
) -> Result<()>
170+
where
171+
R: RegistryStorage,
172+
C: ContentStorage,
173+
N: NamespaceMapStorage,
174+
{
165175
let release = info.state.releases().last();
166176
if let Some(r) = release {
167177
let state = &r.state;

0 commit comments

Comments
 (0)